HSG

Aktuelle Seite: HSG/Fächer/Informatik/Python/tkinter

Template 1 - EVA

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.

EVA 1 add1.py

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

  • self.title("Addition 1") legt den Fenstertitel fest
  • self.geometry('230x120+400+100') legt die Fensterbreite und - höhe, sowie den linken und den oberen Abstand fest.
  • Das erste Eingabefeld (Entry) heißt eA und ist mit dem Default-Eintrag 12.7 belegt. self.eA.place(x=20, y=20, width=50) legt die Abstände bezüglich des Fensters und die Breite fest.
  • Der Button bRechne wird durch self.bRechne = Button(master=self, text="berechne", command=self.callback) mit 'berechne' beschriftet und mit der Ereignisbehandlung callback verbunden. callback ist ein formaler Parameter für den aktuellen Parameter, der später bei Erschaffung eines Objekts der Klasse View eingesetzt wird.
  • Der Controller erschafft einen View und legt die konkrete Ereignisbehandlung fest. Zunächst müssen dabei die Strings der Eingabefelder geholt und in Zahlen umgewandelt werden. Die Umwandlung erledigt eval.
  • Die Programmiererin bzw. der Programmierer schreibt den eigentlichen Verarbeitungsteil.
  • Die Ausgabe geschieht in der Vorlage durch das Label lC. Hier wird dann auch der Zahlenwert s in einen String verwandelt.
  • Die Erschaffung eine Controller-Objekts startet das eigentliche 'Programm'. Man sieht sehr schön, dass der Konstruktor des Controllers nötige Objekte und nötige Verknüpfungen erschafft.

Template 2 - MVC

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.

MVC1 mvc1.py

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

Template 3 - Timer, Canvas, sauberes Beenden

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.

Taster 1 taster1.py

# 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()

Links

Valid XHTML 1.0!