繁体   English   中英

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

[英]Python 2.7 - How do I use an Observer in a Tkinter GUI, where you switch between frames?

我目前正在开发一个基于线程如何从类中获取可变数据的GUI。 由于将要处理大量数据,我想使用Model-Class,它通过Observer得到它的更新。

现在, ttk.Combobox页上的ttk.Combobox更改通过<<ComboboxSelect>>注册,被拉入Controller的变量self.shared_data并传递给Model 这样,就不会使用Oberserver / Observable逻辑。 相反,只要用户在GUI中执行相应的操作,就会更改Model的数据。

但是,我希望不必使用像<<ComboboxSelect>>这样的绑定来更改Model的相应数据,而是使用Observer / Observable逻辑来检测字典self.shared_data的条目"Inputformat"Controller中的数据被更改,这反过来刷新Model的数据,即self.model_data ,其中保存了ttk.Combobox的实际状态。

简而言之,我希望通过使用Observer来实现以下目标:

用户在选择即“条目01” ttk.Combobox - > self.shared_data [“Inputformat”]在Controller现在被填入“条目01” - >观察员/可观察逻辑检测到此- >相应的可变在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()

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

第1步:添加模型类

首先,为了使用MVC架构,我们必须将代码分成模型,视图和控件。 在这个例子中,model是class Model: ,control是class PageControl(tk.Tk):和view是页面class StartPage(tk.Frame)PageOne(tk.Frame)PageTwo(tk.Frame)

第2步:设置模型类

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

第3步:向控件类添加推拉方法

现在有一个模型类,我们可以通过例如self.model = Model() PageControl(tk.Tk) self.model = Model()PageControl(tk.Tk) 现在我们有了基本工具来通过例如self.model.set_keys(self.shared_keys)Model设置数据,并从Model获取数据。 既然我们希望我们的控件类能够做到这一点,我们需要一些可以实现这一目标的方法。 因此,我们将推送和拉取方法添加到PageControl (例如def push_key(self) ),然后可以通过控制器从视图(StartPage,PageOne,PageTwo)中进行修改。

第4步:将您的小部件添加到视图类

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

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

第5步:将所有功能都集成到控制器类中

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

第6步:设置绑定和包装器

在这里,我们必须为我们的组合框设置.bind() 由于控制器已经设置为存储数据并且组合框具有文本变量,我们可以使用它通过combobox.bind(<<ComboboxSelect>>)收集有关组合combobox.bind(<<ComboboxSelect>>)状态的信息。 我们所要做的就是设置一个包装器,只要combobox.bind(<<ComboboxSelect>>)抛出一个事件就会调用它。

结束语

现在我们有了一个基于Bryan Oakleys“如何从类中获取可变数据”的示例的程序,该程序使用模型,只要用户在视图中执行相应的操作,就会通过控制器更新该模型。 不幸的是,它没有像第一个预期的那样使用Observer类,但是当我找到一个令人满意的解决方案时,我会继续研究并更新它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM