3

Ich arbeite derzeit an einer GUI, die auf dem Thread How to get variable data from a class basiert. Da es sehr viele Daten geben wird, möchte ich eine Model-Klasse verwenden, die ihre Updates über Observer erhält.Python 2.7 - Wie verwende ich einen Observer in einer Tkinter GUI, wo Sie zwischen Frames wechseln?

Gerade jetzt, Änderungen in der ttk.Combobox auf der ersten Seite über <<ComboboxSelect>> registriert, zog in die Variable self.shared_data des Controller und an den Model. Auf diese Weise wird keine Oberserver/Observable-Logik verwendet. Stattdessen werden die Daten in Model geändert, wenn der Benutzer eine entsprechende Aktion in der GUI ausführt.

Ich aber würde lieben, nicht Bindungen wie <<ComboboxSelect>> zu verwenden zu haben, um die entsprechenden Daten in der Model zu ändern, aber ein Observer/Observable Logik, die, dass der Eintrag "Inputformat" im Wörterbuch self.shared_data im Controller dh erfasst war geändert, was wiederum die Daten in der Model, dh self.model_data aktualisiert, wo der aktuelle Zustand der ttk.Combobox gespeichert ist.

Kurz gesagt, ich möchte folgendes erreichen, von einem Beobachter mit:

Der Benutzer wählt das heißt "Entry 01" im ttk.Combobox -> self.shared_data [ "Inputformat"] in der Controller ist jetzt gefülltem mit "Entry 01" -> eine Observer/Observable-Logik erkennt dies -> die entsprechende Variable in der Model wird geändert.

Damit Sie etwas zum Arbeiten haben, hier ist der Code.

# -*- 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

Ihre Frage ist sehr schwer zu verstehen. Ich empfehle, die Teile "Bearbeiten" und "Re-Edit" sowie "Re-Re-Edit" zu entfernen. Es ist mir egal, wie oft Sie es bearbeitet haben. Wiederholen Sie einfach die ganze Frage, anstatt auf Addendums zu klicken. Sie können wahrscheinlich 3/4 der Wörter in der Frage entfernen und trotzdem Ihren Standpunkt verstehen. –

Antwort

1

Da ich nicht in der Lage war, einen Beobachter zu implementieren Widgets wie die ttk.Combobox zu sehen, habe ich beschlossen, eine Abhilfe zu schaffen. Hier sind die Schritte, die ich unternommen habe, um eine MVC-Architektur von Bryan Oakleys Beispiel zu erreichen (Link ist in der Frage), die ihre Modellklasse über die Controller-Klasse aktualisiert, wenn ein Benutzer eine Aktion in der Ansicht (GUI) ausführt.

Schritt 1: Modellklasse

Erste hinzufügen, um eine MVC-Architektur zu nutzen, haben wir den Code in Modell, Ansicht und Kontrolle zu trennen. In diesem Beispiel ist das Modell class Model:, die Kontrolle ist class PageControl(tk.Tk): und die Seiten class StartPage(tk.Frame), PageOne(tk.Frame) und PageTwo(tk.Frame) sind zu sehen.

Schritt 2: Richten Sie Ihre Modellklasse

Jetzt müssen wir entscheiden, welche Variablen, die wir in der Modellklasse haben wollen. In diesem Beispiel haben wir Verzeichnisse und Schlüssel (Status der Comboboxen), die wir in Wörterbüchern speichern wollen. Nachdem wir sie leer gemacht haben, müssen wir nur noch Setter und Getter für jede Variable hinzufügen, damit wir die Daten im Modell aktualisieren und auch einige abrufen können, wenn wir wollen. Außerdem könnten wir Delet-Methoden für jede Variable implementieren, wenn wir das wollten.

Schritt 3: Push-In und

Jetzt Methoden an die Steuerklasse ziehen, dass es eine Modellklasse ist, können wir sie per E refrence. G. self.model = Model() in PageControl(tk.Tk) (Kontrolle). Jetzt haben wir die grundlegenden Werkzeuge, um Daten in Model über e zu setzen. G. self.model.set_keys(self.shared_keys) und erhalten auch Daten von Model.Da unsere Kontrollklasse das tun soll, brauchen wir einige Methoden, die dies erreichen können. Also fügen wir die Push- und Pull-Methoden zur PageControl hinzu (z. B. def push_key(self)), die wiederum über den Controller (StartPage, PageOne, PageTwo) über den Controller abgerufen werden kann.

Schritt 4: Fügen Sie Widgets in die Ansichtsklasse

Jetzt müssen wir entscheiden, welche Widgets auf welcher Seite sein soll und was Sie wollen, dass sie tun. In diesem Beispiel gibt es Schaltflächen für die Navigation, die für die Aufgabe ignoriert werden können, zwei Comboboxen und eine Schaltfläche, die einen Dateidialog öffnet.

Hier möchten wir, dass die Comboboxen ihren Status aktualisieren, wenn sie geändert werden, und den neuen Status per Controller an das Modell senden. Während die Open Schaltfläche von PageOne einen Dateidialog öffnen soll, in dem der Benutzer Dateien auswählt, die er öffnen möchte. Die Verzeichnisse, die wir von dieser Interaktion erhalten haben, sollen dann über den Controller an das Modell gesendet werden.

Schritt 5: Holen Sie sich alle Ihre Funktionalität in die Controller-Klasse

Da es eine Steuerungsvariable ist, können wir es verwenden Methoden refrence, die in der Controller-Klasse sind. Auf diese Weise können wir alle unsere Methoden von den Seiten in den Controller auslagern und sie über self.controller.function_of_controller_class referenzieren. Aber wir müssen uns bewusst sein, dass Methoden, die über lambda: an Befehle gebunden sind, keine Werte zurückgeben können, aber auch beim Programmstart nicht aufgerufen werden. Behalte das im Hinterkopf.

Schritt 6: Richten Sie Ihre Bindungen und Wrapper

Hier müssen wir die .bind() für unsere Comboboxen einrichten. Da der Controller bereits bereit ist Daten zu speichern und die Comboboxen eine Textvariable haben, können wir damit Informationen über den Status der Comboboxen über combobox.bind(<<ComboboxSelect>>) sammeln. Alles, was wir tun müssen, ist einen Wrapper einzurichten, der aufgerufen wird, wenn combobox.bind(<<ComboboxSelect>>) ein Ereignis auslöst.

Schlusserklärung

Jetzt haben wir es, ein Programm, das auf Bryan Oakleys Beispiel „Wie variable Daten von einer Klasse zu erhalten“, die ein Modell verwendet, das über den Controller aktualisiert wird, wenn der Benutzer nehmen eine entsprechende Aktion in der Ansicht. Leider benutzt es keine Observer-Klasse, wie ich es ursprünglich geplant hatte, aber ich werde weiter daran arbeiten und es aktualisieren, wenn ich eine befriedigende Lösung gefunden habe.