3

我目前正在基於線程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的單詞,並且仍然可以解決問題。 –

回答

1

由於我無法實現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類,但是當我找到一個令人滿意的解決方案時,我會繼續研究並更新它。