HSG |
|
Das Gerüst einer einfachen EVA-Anwendung wird vorgegeben. Die Benutzungsoberfläche (GUI) wird durch ein Tkinter-Fenster gebildet, das Eingabe-Felder, einen Button und ein Ausgabefeld enthält. Die Ereignisbehandlungs-Routine (callback) ist vorgegeben und braucht nur noch im Verarbeitungsteil an die eigenen Bedürfnisse angepasst werden.
# -*- coding: iso-8859-1 -*- # mk, 8.10.09 Python 2.6 # view from Tkinter import * class View(Tk): def __init__(self,callback): Tk.__init__(self) self.callback = callback # Fenster self.title("Addition 1") self.geometry('230x120+400+100') # Entries self.eA = Entry(master=self) self.eA.insert(0, '12.7') self.eA.place(x=20, y=20, width=50) self.eB = Entry(master=self) self.eB.insert(0, '-18') self.eB.place(x=80, y=20, width=50) # Button self.bRechne = Button(master=self, text="berechne", command=self.callback) self.bRechne.place(x=20, y=50, width=50) # Label self.lC = Label(master=self, text='??') self.lC.place(x=20, y=80) # controller class Controller(object): def __init__(self): self.view = View(self.berechne) self.berechne() # zur Initialisierung self.view.mainloop() def berechne(self): # Eingabe a = eval(self.view.eA.get()) b = eval(self.view.eB.get()) # Verarbeitung # ------------------ hier die Verarbeitung programmieren ------------ s = a + b # ------------------------------------------------------------------- # Ausgabe self.view.lC.config(text=str(s)) # Hauptprogramm c = Controller()
Die Eigenschaften der beteiligten Objekte werden direkt im Quelltext gesetzt.
In einer typischen MVC -Modellierung wird das Fachkonzept in einem 'model'-Objekt gehalten, die Benutzungsoberfläche im 'view'-Objekt. Es wird angestrebt, dass 'model' und 'view' nichts voneinander wissen müssen. Das Zusammenspiel der beiden Objekte wird völlig vom 'controller'-Objekt organisiert. Der 'controller' implementiert und setzt dafür die in 'model' und 'view' vorgesehenen Callbacks (Ereignisbehandlungsroutinen). Man beachte, dass 'view' nicht dadurch, dass es Elemente enthält, über die eine Kontrolle ausgeübt werden kann, zum 'controller' wird. 'view' enthält nur die Bedienelemente.
# -*- coding: iso-8859-1 -*- # mk, 9.10.09 Python 2.6 # model class Model(object): def __init__(self,callback): self.OnChange = callback self.obj = None def set(self,wert): self.obj = wert # an entsprechenden Stellen OnChange aufrufen meldung = str(self.obj) if self.OnChange != None: self.OnChange(meldung) # view from Tkinter import * class View(Tk): def __init__(self): Tk.__init__(self) self.callback = None # Fenster self.title("MVC 1") self.geometry('230x120') # Entries self.eA = Entry(master=self) self.eA.insert(0, '170') self.eA.place(x=20, y=20, width=50) # Buttons self.bSchreibe = Button(master=self, text="schreibe") self.bindung = self.bSchreibe.bind('',self.callback) self.bSchreibe.place(x=20, y=50, width=50) # Labels self.lG = Label(master=self, text='??') self.lG.place(x=20, y=80) def setCallback(self, callback): self.bSchreibe.unbind('',self.bindung) self.bindung = self.bSchreibe.bind('',callback) # controller class Controller(object): def __init__(self): self.view = View() self.view.setCallback(self.schreibe) self.model = Model(self.zeige) self.view.mainloop() def schreibe(self,event): wert = eval(self.view.eA.get()) self.model.set(wert) def zeige(self,meldung): self.view.lG.config(text=meldung) # Hauptprogramm c = Controller()
Die Callbacks kann man auf zwei Arten setzen. Am einfachsten ist es, dem Konstruktor den Namen der Callback-Routine zu übergeben. Mit diesem Namen können auch notwendige Bindungen gesetzt werden. Sollen Bindungen an Tkinter-Ereignisse während der Laufzeit verändert werden, so muss man wie im Beispiel gezeigt, die Bindungen verwalten. Jetzt können jederzeit Callbacks gesetzt werden. Typisch für ein 'model'-Callback ist eine OnChange-Routine, die immer dann aufgerufen wird, wenn sich im 'model' etwas Interessantes verändert hat. Man kann für verschiedene Änderungen jeweils eigene Callbacks verwenden oder aber OnChange eine Meldung mitgeben, die dann ausgewertet werden kann. Die zweite Möglichkeit hat sich in der Praxis bewährt. Tkinter-Ereignisse schicken ein 'event'-Objekt mit, das zusätzliche Informationen z.B. über Maus-Koordinaten enthält. Man muss dieses Objekt bei der Implementierung berücksichtigen, auch wenn man es nicht auswertet.
Python benötigt wegen der Garbage Collection eigentlich keine Destruktoren. Es gibt aber Gelegenheiten, wo man sich wünscht, gewisse Aufräumarbeiten zu machen, bevor ein Objekt verschwindet. Der Destruktor __del__ ist für diesen Zweck gedacht. Er wird aufgerufen, wenn die letzte Referenz auf das Objekt verschwindet. Im vorliegenden Fall wird die serielle Schnittstelle auch im Falle eines Programmabsturzes sicher geschlossen.
Beim Beenden eines Programms gibt es oft einiges aufzuräumen. Wird das Fenster durch Anklicken des X-Symbols geschlossen, so will der Benutzer oft das Programm beenden, was aber nicht der Fall ist. Es ist nun möglich, den Handler für WM_DELETE_WINDOW abzufangen und durch eigenen Code zu ersetzen.
Es ist ein Grundproblem, wie man den Wert einer externen Größe laufend aktualisiert bekommt. Ein einfaches Verfahren ist das sogenannte Polling, dh. es wird periodisch nachgefragt. Dieses Nachfragen darf nicht in einer Schleife geschehen, da dadurch der Prozessor zu sehr ausgelastet wird. Das Polling wird besser Timer-gesteuert. Von der Logik her gehört der Timer als eigenständiger Thread in das Model. Jetzt kann aber der Fall auftreten, dass von Model eine Aktualisierung im View zum 'falschen Zeitpunkt' erfolgt, dh. View darf gerade nicht unterbrochen werden. Tut man es doch, so kann ein Programmabsturz die Folge sein. Tkinter ist in diesem Sinn nicht threadsicher. Vermutlich aus diesem Grund verfügt Tkinter wie z.B. auch die VCL von Delphi über einen eigenen Timer. Dieser Timer kann als Bestandteil von Tkinter entsprechend kontrolliert werden. Es gibt noch weitere Ansätze, Probleme dieser Art zu vermeiden, z.B. kann in Tkinter über after_idle eine Aktualisierung verzögert werden.
# Coding in Python 3.1 immer utf-8 # mk, 23.10.09 , Python 3.1 # model import serial class Model(object): def __init__(self): self.s = serial.Serial(1) self.s.setRTS(False) def __del__(self): # schließt sicher die Schnittstelle self.s.close() # view from tkinter import * class View(Tk): def __init__(self,cbEin,cbAus,cbCTS,cbHalt): Tk.__init__(self) # Callbacks self.cbEin = cbEin self.cbAus = cbAus self.cbCTS = cbCTS self.protocol("WM_DELETE_WINDOW",cbHalt) # cbHalt wird aufgerufen, wenn das 'X' gedrückt wird # Fenster self.title("Taster 1") self.geometry('200x100') # Button self.bTaster = Button(master=self, text="Taster") self.bTaster.bind('<Button-1>',self.cbEin) self.bTaster.bind('<ButtonRelease>',self.cbAus) self.bTaster.place(x=30,y=37) # Canvas self.c = Canvas(master=self,width=60,height=60) self.c.place(x=120,y=20) # Items item = self.c.create_oval(10,10,50,50,fill='#550000') self.c.itemconfig(item,tags=('LED')) # Timer starten self.after(0,self.poll) def poll(self): if self.cbCTS(): self.c.itemconfig('LED',fill='#ff0000') else: self.c.itemconfig('LED',fill='#550000') self.after(20,self.poll) # rekursiver Aufruf # controller class Controller(object): def __init__(self): self.model = Model() self.view = View(self.schalteEin,self.schalteAus,self.model.s.getCTS,self.Halt) self.view.mainloop() def schalteEin(self,event): self.model.s.setRTS(True) def schalteAus(self,event): self.model.s.setRTS(False) def Halt(self): # Aufräumarbeiten self.model.s.close() # Schnittstelle schließen self.view.quit() # mainloop beenden self.view.destroy() # Fenster beseitigen # Hauptprogramm c = Controller()