Python 2.7 - 如何在Tkinter GUI中使用Observer,在哪些帧之间切换?

Python 2.7 - 如何在Tkinter GUI中使用Observer,在哪些帧之间切换?

问题描述:

我目前正在基于线程How to get variable data from a class的GUI上工作。由于会有大量的数据需要处理,我想使用一个Model-Class,它通过Observer得到它的更新。Python 2.7 - 如何在Tkinter GUI中使用Observer,在哪些帧之间切换?

眼下,在页面的一个变化在ttk.Combobox通过<<ComboboxSelect>>注册,被拉进Controller的变量self.shared_data并传递给Model。这样,不使用Oberserver/Observable逻辑。相反,只要用户在GUI中采取相应的操作,就会更改Model中的数据。

I,然而,会喜欢不必须使用绑定像<<ComboboxSelect>>以改变Model相应的数据,但是观察员/可观察的逻辑,用于检测,即,即条目"Inputformat"在词典self.shared_dataController内是然后刷新Model的数据,即self.model_data,其中保存了ttk.Combobox的实际状态。

总之,我要实现以下,通过使用观测器:

用户在ttk.Combobox选择即“条目01” - > self.shared_data [“Inputformat”]在Controller现在充满用“Entry 01” - > Observer/Observable逻辑检测到 - >Model中的相应变量正在改变。

为了让您有一些工作,这里是代码。

# -*- coding: utf-8 -*- 

import csv 
import Tkinter as tk # python2 
import ttk 
import tkFileDialog 


# Register a new csv dialect for global use. 
# Its delimiter shall be the semicolon: 
csv.register_dialect('excel-semicolon', delimiter = ';') 

font = ('Calibri', 12) 

''' 
############################################################################### 
#         Model          # 
############################################################################### 
''' 

class Model: 
    def __init__(self, *args, **kwargs): 
     # There shall be a variable, which is updated every time the entry 
     # of the combobox is changed 
     self.model_keys = {} 
     self.model_directories = {} 

    def set_keys(self, keys_model): 
     self.model_keys = keys_model 
     keys = [] 
     keyentries = [] 
     for key in self.model_keys: 
      keys.append(key) 
     for entry in self.model_keys: 
      keyentries.append(self.model_keys[entry].get()) 

     print "model_keys: {0}".format(keys) 
     print "model_keyentries: {0}".format(keyentries) 

    def get_keys(self): 
     keys_model = self.model_keys 
     return(keys_model) 

    def set_directories(self, model_directories): 
     self.model_directories = model_directories 
     print "Directories: {0}".format(self.model_directories) 

    def get_directories(self): 
     model_directories = self.model_directories 
     return(model_directories) 


''' 
############################################################################### 
#        Controller         # 
############################################################################### 
''' 

# controller handles the following: shown pages (View), calculations 
# (to be implemented), datasets (Model), communication 
class PageControl(tk.Tk): 

    ''' Initialisations ''' 
    def __init__(self, *args, **kwargs): 
     tk.Tk.__init__(self, *args, **kwargs) # init 
     tk.Tk.wm_title(self, "MCR-ALS-Converter") # title 

     # initiate Model 
     self.model = Model() 

     # file dialog options 
     self.file_opt = self.file_dialog_options() 

     # stores checkboxstatus, comboboxselections etc. 
     self.shared_keys = self.keys() 

     # creates the frames, which are stacked all over each other 
     container = self.create_frame() 
     self.stack_frames(container) 

     #creates the menubar for all frames 
     self.create_menubar(container) 

     # raises the chosen frame over the others 
     self.frame = self.show_frame("StartPage")  


    ''' Methods to show View''' 
    # frame, which is the container for all pages 
    def create_frame(self):   
     # the container is where we'll stack a bunch of frames 
     # on top of each other, then the one we want visible 
     # will be raised above the others 
     container = ttk.Frame(self) 
     container.pack(side="top", fill="both", expand=True) 
     container.grid_rowconfigure(0, weight=1) 
     container.grid_columnconfigure(0, weight=1) 
     return(container) 

    def stack_frames(self, container): 
     self.frames = {} 
     for F in (StartPage, PageOne, PageTwo): 
      page_name = F.__name__ 
      frame = F(parent = container, controller = self) 
      self.frames[page_name] = frame 
      # put all of the pages in the same location; 
      # the one on the top of the stacking order 
      # will be the one that is visible. 
      frame.grid(row=0, column=0, sticky="nsew") 

    # overarching menubar, seen by all pages 
    def create_menubar(self, container):  
     # the menubar is going to be seen by all pages  
     menubar = tk.Menu(container) 
     menubar.add_command(label = "Quit", command = lambda: app.destroy()) 
     tk.Tk.config(self, menu = menubar) 

    # function of the controller, to show the desired frame 
    def show_frame(self, page_name): 
     #Show the frame for the given page name 
     frame = self.frames[page_name] 
     frame.tkraise() 
     return(frame) 


    ''' Push and Pull of Data from and to Model ''' 
    # calls the method, which pushes the keys in Model (setter) 
    def push_keys(self): 
     self.model.set_keys(self.shared_keys) 

    # calls the method, which pulls the key data from Model (getter)  
    def pull_keys(self): 
     pulled_keys = self.model.get_keys() 
     return(pulled_keys) 

    # calls the method, which pushes the directory data in Model (setter) 
    def push_directories(self, directories): 
     self.model.set_directories(directories) 

    # calls the method, which pulls the directory data from Model (getter) 
    def pull_directories(self): 
     directories = self.model.get_directories() 
     return(directories) 


    ''' Keys ''' 
    # dictionary with all the variables regarding widgetstatus like checkbox checked  
    def keys(self): 
     keys = {} 
     keys["Inputformat"] = tk.StringVar() 
     keys["Outputformat"] = tk.StringVar() 
     return(keys) 


    ''' Options ''' 
    # function, which defines the options for file input and output  
    def file_dialog_options(self): 
     #Options for saving and loading of files: 
     options = {} 
     options['defaultextension'] = '.csv' 
     options['filetypes'] = [('Comma-Seperated Values', '.csv'), 
           ('ASCII-File','.asc'), 
           ('Normal Text File','.txt')] 
     options['initialdir'] = 'C//' 
     options['initialfile'] = '' 
     options['parent'] = self 
     options['title'] = 'MCR-ALS Data Preprocessing' 
     return(options) 


    ''' Methods (bindings) for PageOne ''' 
    def open_button(self): 
     self.get_directories() 


    ''' Methods (functions) for PageOne ''' 
    # UI, where the user can selected data, that shall be opened 
    def get_directories(self): 
     # open files 
     file_input = tkFileDialog.askopenfilenames(** self.file_opt) 
     file_input = sorted(list(file_input)) 
     # create dictionary 
     file_input_dict = {} 
     file_input_dict["Input_Directories"] = file_input 
     self.push_directories(file_input_dict) 


''' 
############################################################################### 
#         View          # 
############################################################################### 
''' 


class StartPage(ttk.Frame): 

    ''' Initialisations ''' 
    def __init__(self, parent, controller): 
     ttk.Frame.__init__(self, parent) 
     self.controller = controller 

     self.labels() 
     self.buttons() 


    ''' Widgets '''   
    def labels(self): 
     label = tk.Label(self, text = "This is the start page", font = font) 
     label.pack(side = "top", fill = "x", pady = 10) 

    def buttons(self): 
     button1 = ttk.Button(self, text = "Go to Page One", 
          command = lambda: self.controller.show_frame("PageOne")) 
     button2 = ttk.Button(self, text = "Go to Page Two", 
          command = lambda: self.controller.show_frame("PageTwo")) 
     button_close = ttk.Button(self, text = "Close", 
           command = lambda: app.destroy())      
     button1.pack(side = "top", fill = "x", pady = 10) 
     button2.pack(side = "top", fill = "x", pady = 10) 
     button_close.pack(side = "top", fill = "x", pady = 10) 


class PageOne(ttk.Frame): 

    ''' Initialisations ''' 
    def __init__(self, parent, controller): 
     ttk.Frame.__init__(self, parent) 
     self.controller = controller 

     self.labels() 
     self.buttons() 
     self.combobox() 

    ''' Widgets ''' 
    def labels(self): 
     label = tk.Label(self, text = "On this page, you can read data", font = font) 
     label.pack(side = "top", fill = "x", pady = 10) 

    def buttons(self): 
     button_open = ttk.Button(self, text = "Open", 
           command = lambda: self.controller.open_button()) 
     button_forward = ttk.Button(self, text = "Next Page >>", 
           command = lambda: self.controller.show_frame("PageTwo")) 
     button_back = ttk.Button(self, text = "<< Go back", 
           command = lambda: self.controller.show_frame("StartPage")) 
     button_home = ttk.Button(self, text = "Home", 
           command = lambda: self.controller.show_frame("StartPage")) 
     button_close = ttk.Button(self, text = "Close", 
           command = lambda: app.destroy()) 
     button_open.pack(side = "top", fill = "x", pady = 10) 
     button_forward.pack(side = "top", fill = "x", pady = 10) 
     button_back.pack(side = "top", fill = "x", pady = 10) 
     button_home.pack(side = "top", fill = "x", pady = 10) 
     button_close.pack(side = "top", fill = "x", pady = 10) 

    def combobox(self):         
     entries = ("", "Inputformat_01", "Inputformat_02", "Inputformat_03") 
     combobox = ttk.Combobox(self, state = 'readonly', values = entries, 
            textvariable = self.controller.shared_keys["Inputformat"]) 
     combobox.current(0) 
     combobox.bind('<<ComboboxSelected>>', self.updater) 
     combobox.pack(side = "top", fill = "x", pady = 10) 


    ''' Bindings ''' 
    # wrapper, which notifies the controller, that it can update keys in Model 
    def updater(self, event): 
     self.controller.push_keys() 



class PageTwo(ttk.Frame): 

    ''' Initialisations ''' 
    def __init__(self, parent, controller): 
     ttk.Frame.__init__(self, parent) 
     self.controller = controller 

     self.labels() 
     self.buttons() 
     self.combobox() 


    ''' Widgets '''   
    def labels(self): 
     label = tk.Label(self, text = "This is page 2", font = font) 
     label.pack(side = "top", fill = "x", pady = 10) 

    def buttons(self): 
     button_back = ttk.Button(self, text = "<< Go back", 
           command = lambda: self.controller.show_frame("PageOne")) 
     button_home = ttk.Button(self, text = "Home", 
           command = lambda: self.controller.show_frame("StartPage")) 
     button_close = ttk.Button(self, text = "Close", 
           command = lambda: app.destroy())       
     button_back.pack(side = "top", fill = "x", pady = 10) 
     button_home.pack(side = "top", fill = "x", pady = 10) 
     button_close.pack(side = "top", fill = "x", pady = 10) 

    def combobox(self): 
     entries = ("Outputformat_01", "Outputformat_02") 
     combobox = ttk.Combobox(self, state = 'readonly', values = entries, 
            textvariable = self.controller.shared_keys["Outputformat"]) 
     combobox.bind('<<ComboboxSelected>>', self.updater) 
     combobox.pack(side = "top", fill = "x", pady = 10) 


    ''' Bindings ''' 
    # wrapper, which notifies the controller, that it can update keys in Model 
    def updater(self, event): 
     self.controller.push_keys() 



if __name__ == "__main__": 
    app = PageControl() 
    app.mainloop() 
+0

你的问题很难理解。我建议删除“编辑”和“重新编辑”和“重新编辑”部分。我不在乎你编辑过多少次。只是重申整个问题而不是追加附录。你可以删除问题中3/4的单词,并且仍然可以解决问题。 –

由于我无法实现Observer来观看像ttk.Combobox这样的小部件,因此我决定创建一个解决方法。下面是我采取的步骤,以便通过Bryan Oakleys示例(链接在问题中)实现MVC体系结构,每当用户在视图(GUI)中执行操作时,都会通过控制器类刷新其模型类。

第1步:添加一个模型类

首先,为了使用MVC架构,我们有单独的代码转换成模型,视图和控制。在这个例子中,型号是class Model:,控制是class PageControl(tk.Tk):,查看的是页面class StartPage(tk.Frame),PageOne(tk.Frame)PageTwo(tk.Frame)

第2步:设置你的模型类

现在我们必须对我们要在模型类变量决定。在这个例子中,我们有目录和键(组合框的状态),我们要保存在字典中。将它们设置为空之后,我们所要做的就是为每个变量添加setter和getters,这样我们就可以刷新模型中的数据,并且还可以检索一些数据(如果需要的话)。另外,如果我们想要的话,我们可以为每个变量实施删除方法。

3步:添加推拉的方法来控制类

现在有一个模型类,我们可以通过e refrence它。 G。 self.model = Model() in PageControl(tk.Tk)(对照)。现在我们有基本的工具来通过e在Model中设置数据。 G。 self.model.set_keys(self.shared_keys),并从Model获得数据。既然我们想要我们的控制类来做到这一点,我们需要一些方法,可以实现这一点。因此,我们将推拉方法添加到PageControl(例如def push_key(self)),后者又可以通过控制器从视图(起始页,PageOne,PageTwo)中引用。

第4步:添加小部件视图类

现在我们必须决定哪些部件应是哪个网页上,你希望他们做什么。在这个例子中,有用于导航的按钮,为了该任务可以忽略它们,两个组合框和一个按钮,用于打开文件对话框。

在这里,我们希望组合框在其更改时刷新其状态,并通过控制器将新状态发送到模型。 PageOneOpen按钮将打开文件对话框,然后用户选择他/她想要打开的文件。我们从这个交互中获得的目录应该通过控制器发送给模型。

第5步:您的所有功能集成到控制器类

既然有控制变量,我们可以用它来refrence方法,即在控制类。这样,我们可以将所有我们的方法从页面外包到控制器中,并通过self.controller.function_of_controller_class引用它们。但我们必须知道,通过lambda:绑定到命令的方法不能返回任何值,但它们在程序启动时也不会被调用。所以记住这一点。

第6步:设置您的绑定和包装

下面我们就来建立.bind()我们的组合框。由于控制器allready已设置为存储数据,并且组合框具有文本变量,因此我们可以通过combobox.bind(<<ComboboxSelect>>)来收集有关组合框状态的信息。我们所要做的就是设置一个被称为的包装,无论何时combobox.bind(<<ComboboxSelect>>)正在抛出一个事件。

闭幕词

现在我们把它的基础上,布赖恩·奥克利例子程序“如何从一个类变量数据”,它采用了模型,该模型通过控制器更新每当用户需要视图中的相应操作。不幸的是,它并没有像第一次打算那样利用Observer类,但是当我找到一个令人满意的解决方案时,我会继续研究并更新它。