GUI - Programmierung mit Tkinter

Bei diesem Thema betrachten wir, wie ein GUI-Programm im Allgemeinen zusammengesetzt ist und dann, wie dies unter der Verwendung von Pythons natürlicher Werkzeugsammlung, Tkinter, zustande kommt. Dabei ergibt sich weder eine übergroße Tkinter-Referenz, noch ein komplettes Tutorium. Es gibt schon eine seht gutes und detailliertes Tutorium, das man über die Python-Webseite erreichen kann. Unser Tutorium hingegen wird versuchen, dich durch die Grundlagen der GUI-Programmierung zu lotsen, indem es in einige grundlegende GUI-Kompenenten einführt und zeigt,wie diese zu gebrauchen sind. Wir werden uns auch anschauen, wie Objek-Orientierte Programmierung helfen kann, eine GUI-Anwendung zu organisieren.

Prinzipielles über GUI

Die erste Sache, die ich erwähnen möchte, ist dass wir hier nichts Neues über das Programmieren lernen werden. Die Programmierung eines GUI ist genauso wie jede andere Art der Programmierung; du kannst dabei genauso wie vorher Befehlsfolgen, Schleifen, Sprünge und Module verwenden.Der einzige Unterschied in der GUI-Programmierung ist der, dass du normalerweise einen Werkzeugsatz (toolkit) verwenden musst, das dem Muster folgt, dass vom Hersteller des Werkzeugsatzes angelegt worden ist. Jeder neue Werkzeugsatz wird sein eigenes API haben, die Design-Regeln vorgeben und du als Programmierer hast alles zu erlernen. Deshalb versuchen die meisten Programmierer dies auf einige wenige Werkzeugsätze zu standardisieren, die querbeet von vielen Programmiersprachen zugänglich sind - das Erlernen eines neuen Werkzeugsatzes ist härter, als das Erlernen einer neuen Programmiersprache!

Wir werden uns den Tk-Werkzeugsatz anschauen, der in Tcl, Perl und Python verwendet wird. Die Prinzipien von Tk sind etwas unterschiedlich zu anderen Toolkits, so dass ich Eine Zusammenfassung mit einem kurzen Blick auf ein anderes populäres GUI-Toolkit geben werde, das für Python (und C/C++) Anwendung findet und etwas konventioneller in seiner Handhabung ist. Aber zuerst einige prinzipielle Dinge:

Wie wir schon mehrmals festgestellt haben, sind GUI-Applikationen fast immer von Natur aus ereignisgesteuert. Wenn du dich nicht mehr erinnern kannst, was das bedeutet, dann gehe nochmals zurück zum Kapitel über Ereignisgesteuerte Programmierung.

Ich werde voraussetzen, dass du schon als Anwender mit GUI's vertraut bist und will den Blick darauf konzentrieren, wie GUI-Programme aus der Perspektive des Programmierers aus funktionieren. Ich werde nicht ins Details, wie das Schreiben großer komplexer GUIs mit mehrfachen Fenstern, MDI - Schnittstellen usw., gehen. Ich bleibe lediglich bei den Grundlagen zur Erzeugung einer einzelnen Fenster-Applikation mit einigen Labels, Buttons, Textboxen und Messageboxen.

Das Erste zuerst und dazu müssen wir unseren Wortschatz überprüfen. GUI - Programmierung hat einen eigenen Bestand an Programmierbegriffen. Die am häufigsten verwendeten Begriffe sind in der folgenden Tabelle beschrieben:

Begriff Beschreibung
Window (Fenster) Ein Bildschirmbereich, der von einer Applikation kontrolliert wird. Windows sind gewöhnlich rechteckig, aber einige GUI-Umgebungen erlauben andere Formen. Windows können andere Windows enthalten und oft wird jedes einzelne GUI-Kontrollelement so behandelt, als hätte es die gleichen Rechte, wie ein Window.
Control

(Kontroll -element)

Ein Kontrollelement ist ein GUI-Objekt, das zur Steuerung der Anwendung verwendet wird. Kontrollelemente haben bestimmte Eigenschaften und erzeugen gewöhnlich Ereignisse. Normalerweise entsprechen Kontrollelemente Objekten auf der Applikationsebene und die Ereignisse sind mit den Methoden des zugehörigen Objekts gekoppelt, so dass, wenn ein Ereignis auftritt, das Objekt eines seiner Methoden ausführt. Die GUI - Umgebung unterstützt üblicherweise einen Mechanismus zur Bindung von Ereignissen an Methoden.
Widget Der Begriff Kontrollelement ist manchmal beschränkt auf visuelle Steuerelemente. Einige Kontollelemente (so wie Timer) können mit Fenstern in Verbindung gebracht werden, sind aber nicht sichtbar. Widgets sind eine solche Unterordnung von Kontrollelementen, die sichtbar sind und vom Anwender oder Programmierer manipuliert werden können. Wir werden hier folgende Widgets behandeln:
  • Frame
  • Label
  • Button
  • Text Entry
  • Message boxes

Einige werden nicht hier in diesem Kapitel besprochen, wurden aber irgendwo sonst im Tutorium erwähnt:

  • Text box
  • Radio Button

Schließlich diejenigen, die überhaupt nicht besprochen werden:

  • Canvas - zum Zeichnen
  • Check button - für mehrfache Auswahl
  • Image - zum Anzeigen von BMP, GIF, JPEG und PNG Bildern
  • Listbox - für Listen!
  • Menu/MenuButton - zur Erzeugung von Menüs
  • Scale/Scrollbar - zur Anzeige von Positionen
Frame (Rahmen) Dies ist ein Widget-Typ, der zur Gruppierung anderer Widgets dient. Ofmals representiert ein Frame ein komplettes Fenster und andere Frames sind darin eingebettet.
Layout (Gestalt) Controls werden innerhalb eines Frame mit einer zugehörigen ganz bestimmten Gestaltung festgelegt. Diese Gestaltgebung kann auf eine Vielzahl von Arten spezifiziert werden, entweder durch Bildschirmkoordinaten in Pixel ausgedrückt, oder durch Relativpositionen bezüglich anderer Komponenten (links, oben usw.) oder durch die Verwendung einer Gitter- oder Tabellenanordnung. Ein Koordinatensystem ist leicht zu verstehen, aber schwierig zu handhaben, z. B. bei der Größenänderung eines Fensters. Anfängern wird empfohlen, in ihrer Größe unveränderliche Windows zu verwenden, falls sie mit einer koordinatenbasierten Gestaltunsweise arbeiten.
Child (Kind) GUI-Anwendungen neigen dazu, aus einer Hierarchie von Widgets oder Kontrollelementen zu bestehen. Das übergeordente Frame, das das Applikationsfenster representiert, enthält möglicherweise wieder untergeordnete Frames, die ihrerseits noch mehr Frames und Controls beinhalten. Dieses Verhältnis der Kontrollelemente kann in Form einer Baumstruktur verdeutlicht werden, worin jedes Kontrollelement ein einzelnes Elternteil und mehrere Kinder besitzt. Tatsächlich ist es bei einer solchen Struktur normal, dass sie explizit durch die Widgets abgespeichert wird, so dass der Programmierer, oder, was häufiger vorkommt, die GUI-Umgebung selbst, die üblichen Aktionen auf ein Kontrollelement und damit auf alle seine Kinder ausführen kann.

Anmerkung des Übersetzers: In einigen Fällen, macht es hier keinen Sinn, die Begriffe ins Deutsche zu übersetzen, da beim Programmieren sowieso die englischen Begriffe verwendet werden müssen.

Die Vorstellung einiger üblicher Widgets

In diesem Kapitel werden wir Python im interaktiven Modus verwenden, um einige einfache Fenster und Widgets zu erzeugen. Beachte, dass keine Tkinter-Applikation richtig innerhalb von IDLE laufen lassen kannst, weil IDLE selbst eine Tkinter-Applikation ist.(*) Dennoch kannst deine Dateien unter verwendung von IDLE als Editor erzeugen,aber denn musst du sie über die Kommandozeile des OS starten. Die Benutzer von Pythonwin können Tkinter-Applikationen laufen lassen, da es das windowseigene Toolkit, MFC, verwendet. Dennoch gibt es sogar in Pythonwin einige bestimmte unerwartete Verhaltensweisen mit Tkinter-Anwendungen. Als Ergebnis aus diesen Erkenntnissen werde ich das Kommandofenster des Betriebssystems (Python Interpreter Shell) als direkte Python-Eingabe verwenden.

(*) Anmerkung des Übersetzers: Diese bemerkung muss sich wohl auf eine ältere Python-/IDLE-Version beziehen. Meine Tkinter-Applikationen können problemlos unter IDLE gestartet werden (Python 2.0 und höher).

>>> from Tkinter import *

Diese Eingabe ist für jedes Tkinter-Programm erforderlich - das Importieren der Widget-Namen. Du kannst auch lediglich das Modul importieren, aber es ist sehr ermüdend, immer Tkinter vor jedem Namensanfang einer Komponente einzugeben.

>>> top = Tk()

Dies erzeugt ein Toplevel-Widget in unserer Widget-Hierarchie. Alle anderen Widgets werden als Kinder von diesem erzeugt. Beachte, dass ein neues leeres Fenster vollständig mit einem leeren Titelbalken und dem üblichen Kontroll-Buttons (minimieren, maximieren usw.) erschienen ist. Wir werden nun Komponenten in dieses Fenster einfügen, als würden wir eine Anwendung erstellen.

>>> dir(top)

['_tclCommands', 'children', 'master', 'tk']

Die dir - Funktion zeigt uns, welche bekannten Namen zu dem Argument gehören. Du kannst sie bei Modulen anwenden, aber in diesem Fall schauen wir uns die Internas des top - Objectes an, eine Instanz der Tk - Klasse. Dies sind die Attribute von top, beachte im Einzelnen die Kind- und Eltern - Attribute, die die verbindungen zure Widghet - Hierarchie sind. Betrachte auch das Attribut _tclCommands, dies kommt daher, wie du dich vielleicht erinnerst, weil Tkinter aus einem Tcl-Werkzeugsatz mit dem Namen Tk erzeugt worden ist.

>>> F = Frame(top)

Erzeugt ein Frame - Widget, das wiederum die child-controls/widgets enthalten wird, die wir verwenden werden. Frame spezifiziert top als seinen ersten (und in diesem Fall einzigen) Parameter, der angibt, dass F ein Kind-Widget von top sein soll.

>>>F.pack()

Beachte, dass das Tk-Window jetzt auf die Größe des eingefügten Frame-Widget geschrumpft ist - das augenblicklich noch leer ist und das Fenster somit sehr klein ist! Die pack() - Methode ruft einen Layout Manager auf, der auch "Packer" genannt wird und sehr leicht für einfache Layouts zu verwenden ist, der aber etwas sperrig bei komplexeren Layouts zu handhaben ist. Wir werden jetzt bei ihm bleiben, weil er leicht zu verwenden ist. Beachte, dass alle Widgets solange in unserer Applikation unsichtbar bleiben, bis wir sie packen (oder eine andere Layout-Methode anwenden).

>>>lHello = Label(F, text="Hello world")

Hier erzeugen wir ein neues Objekt lHello, eine Instanz der Label-Klasse, mit einem eltern-Widget parent widget F und einem text - Attribut "Hello world". Weil die Tkinter-Objekt-Konstruktoren viele Parameter besitzen, wobei jeder einen voreingestellten Wert hat, ist es üblich, die Parameter mit Namen als Argumente dem Tkinter-Objekt zu überegeben. Beachte auch, dass das Objekt noch nicht sichtbar ist, da wir es ja noch nicht gepackt haben.

Ein letzter zu beachtender Punkt ist der Gebrauch einer Namenskonvention: ich habe ein kleingeshriebenes l, für Label, an den Anfang des eines Namens, Hello, geschrieben, und das zeigt mir so seinen zweck an. Wie fast alle Namenskonventionen ist dies eine Sache des persönlichen Geschmacks, aber ich finde es hilft.

>>>lHello.pack()

Jetzt können wir es sehen. Hoffentlich sieht es bei dir in etwa genauso aus, wie dies:

Window with Label

Wir können andere Eigenschaften des Labels spezifizieren, wie Schriftart und Farbe, ebenfalls durch Parameterübergabe an den Objekt-Konstruktor. Wir können auch auf die entsprechenden Eigenschaften zugreifen, wenn wir die configure-Methode des Tkinter-Widgets verwenden, etwa so:

>>> lHello.configure(text="Goodbye")

Die Nachricht hat sich verändert. Das war doch leicht, oder ? configure ist eine besonders gute Technik, wenn du mehrere Eigenschaften auf einmal verändern willst, weil sie alle als Argumente übergeben werden können. Wenn du hingegen nur eine Einzeleigenschaft ändern möchtest, so wie oben, dann kannst du das Objekt wie ein Dictionary behandeln:

>>> lHello['text'] = "Hello again"

was kürzer und sicherlich leichter verständlich ist.

Labels sind ziemlich langweilige Widgets, sie können eigentlich nur Text zum Lesen anzeigen, obgleich auch in verschiedenen Farben, Schriftarten und Größen. (Immerhin können sie zur Anzeige von einfacher Graphik verwendet werden, aber ich zeige dir später wie man das macht.)

Bevor wir uns einen anderen Objekttyp anschauen, ist noch etwas zu erledigen, und zwar den Fenstertitel festzulegen. Dies tun wir durch die Verwendung einer Methode des Toplevel - Widget top:

>>> F.master.title("Hello")

Wir hätten top auch direkt verwenden können, aber wie wir später sehen werden, ist der Zugriff durch die Master-Eigenschaft des Frame eine sehr nützliche Technik.

>>> bQuit = Button(F, text="Quit", command=F.quit)

Hier erzeugen wir ein neues Widget, einen Button. Der Button hat die Aufschrift "Quit" und ist mit dem Befehl F.quit verknüpft. Beachte, dass wir den Methodennamen als Argument übergeben, wir rufen die Methode nicht dadurch auf, indem wir Klammern hintendran setzen. Das bedeutet, wir müssen ein Funktionsobjekt in der Begriffsstruktur von Python übergeben, das kann eine eingebaute Methode, mit der Tkinter ausgestattet ist, wie hier, sein oder oder jede andere von uns definierte Funktion. Die Funktion oder Methode muss keine Argumente übernehmen. Die quit - Methode, wie auch die pack - Method, sind definiert in einer Basis-Klasse und werden von allen Tkinter-Widgets geerbt.

>>>bQuit.pack()

Wiederum macht die pack - Methode den Button sichtbar.

>>>top.mainloop()

Wir starten die Tkinter-Ereignisschleife. Du merkst, dass das Python-Promptzeichen >>> jetzt verschwunden ist. Das sagt uns, dass Tkinter jetzt die Kontrolle übernommen hat. Wenn du nun den Quit - Button drückst, wird das Prompt zurückkehren, das zeigt, dass unsere command - Option funktioniert.

Beachte, ob du ein anderes ergebnis erhälts, wenn du dies von Pythonwin oder IDLE aus startest. Falls dem so ist, schreibe die bisherigen befehle in ein Python-Skript und starte es von einem OS-Befehls-Prompt.

Tatsächlich ist es nun auch an der Zeit, dies einmal zu versuchen, denn es ist eben die Art, wie die meisten Tkinter-Programme in der Praxis ablaufen. Verwende die bisher besprochenen Schlüsselbefehle wie folgt:

from Tkinter import *

# das Fenster selbst festlegen
top = Tk()
F = Frame(top)
F.pack()

# die Widgets hinzufügen
lHello = Label(F, text="Hello")
lHello.pack()
bQuit = Button(F, text="Quit", command=F.quit)
bQuit.pack()

# bringt die Schleife ans Laufen
top.mainloop()

Der Aufruf der top.mainloop - Methode startet die ereigniserzeugende Tkinter-Ereignisschleife. In diesem Fall ist das einigste abzufangende Ereignis das Drücken des Buttons, das mit der F.quit - Methode verbunden ist. F.quit seinerseits beendet die Anwendung. Versuche es, es sollte so aussehen:

Label with Button

Ausprobieren verschiedener Gestaltungsmöglichkeiten

Anmerkung : Um von dem >>> Prompt weg zu kommen, werden Beispiele nur noch innerhalb Python -Skript-Dateien angeführt.

In diesem Abschnitt möchte ich betrachten, wie Tkinter die Widgets innerhalb eines Window positioniert. Wir haben schon Frame-, Label- und Button-Widgets gesehen und das sind alle die wir für diesen Abschnitt benötigen. Im vorangegeangenen Beispiel haben wir die pack - Methode des jeweiligen Widgets verwandt, um es im Eltern-Fenster zu positionieren. Technisch gesehen rufen wir dabei den Tk-Packer Layout Manager auf. Die Aufgabe des Layout Managers ist die beste Gestaltung für die Widgets festzulegen, basierend auf den Hinweisen, die der Programmierer liefert, und zusätzliche Vorschriften, wie die Größe des Fensters, wie sie vom Anwender vorgegeben wird. Einige Layoutmanager verwenden exakte Ortsangaben innerhalb des Window, normalerweise in Pixeln spezifizier; und das ist sehr üblich in Microsoft Windows-Umgebungen wie Visual Basic. Tkinter beinhaltet einen Placer Layout Manager, der dies über eine Plaziermethode tun kann. In diesem Tutorium werden wir uns das nicht anschauen, weil ein anderer, intelligenterer Manager zu diesem Zweck besser geeignet ist, da er uns die Sorge abnimmt, was zu tun ist, wenn eine Größenänderung des Fensters unabhängig von uns als Programmierer auftritt.

Der simpelste Layout-Manager in Tkinter ist der schon verwendete Packer. Der Packer stapelt von sich aus ein Widget auf das andere. Das ist aber in den seltensten Fällen das, was wir für normale Widgets möchten , aber wenn wir unsere Applikation aus Frames zusammenbauen, dann setzen wir ein Frame auf das andere und dann ist der Packer ein sinnvolles Werkzeug. Wir können dann andere Widgets in die Frames bringen und dazu entweder den Packer oder andere Layout-Manager in dem dazugehörigen Frame verwenden. Du kannst dazu ein Beispiel in Aktion bei unserer Fallstudie ansehen.

Dennoch ist der einfache Packer noch mit einer Vielzahl an Optinen ausgestattet. Zum Beispiel können wir unsere Widgets, anstatt sie vertkal anzuordnen, horizontal durch verwendung deines side - Arguments anordnen, wie:

lHello.pack(side="left")
bQuit.pack(side="left")

Dies wird die Widgets nach links zwingen, so dass das erste Widget (das Label) an der äußeren linken Seite erscheint, gefolgt vom nächsten Widget(dem Button). Wenn du die Zeilen wie im oberen Beispiel abänderst, wird das so aussehen:

Left packed widgets

Und wenn du "left" in "right" änderst, erscheint das Label aüßerst rechts und links davon, nämlich so:

Right packed widgets

Eins fällt dir bestimmt auf: Das sieht alles nicht so schön aus, weil die Widgets so zusammengequetscht werden. Der Packer ist mit einigen Parametern ausgestattet, um das zu behandeln. Am leichtesten zu verwenden ist Padding ( 'polstern' auf Deutsch) und wird spezifiziert als horizontales Polstern (padx) und vertikales Polstern(pady). Diese Werte sind in Pixeln spezifiziert. Versuchen wir eine horizontale Polsterung in unserem Beispiel:

lHello.pack(side="left", padx=10)
bQuit.pack(side='left', padx=10)

Das sollte so aussehen:

Horizontal padding

Wenn du versuchst die Fenstergröße zu verändern, wirst du feststellen, dass die Widgets ihre relative Position zueinander behalten aber im Fenster zentriert bleiben. Warum ist das so, haben wir sie nicht nach links gepackt? Die Antwort ist die: Wir haben sie in ein Frame gepackt, aber das Frame wurde ohne eine Seitenangabe gepackt, somit wurde es positioniert auf top, centre- der Defaultwert des Packers. Wenn du möchtest, dass die Widgets auf der richtigen Seite des Fensters bleiben, dann musst du auch das Frame an die entsprechende Seite packen:

F.pack(side='left')

Beachte auch, dass die Widgets zentriert bleiben, wenn du das Fenster vertikal vergrößerst - wieder ist dies das Grundverhalten des Packers. Ich lass dich jetzt selbst etwas mit padx und pady spielen, um den Effekt der unterschiedlichen Werte, Kombinationen usw. zu sehen. Innerhalb dieser erlauben side and padx/pady eine sehr große Flexibilität in der Positionierung von Widgets durch die verwendung des Packers. Es gibt noch verschiedene andere Optionen, die jeweils eine subtile Form der Kontrolle ermöglichen, schau bitte für Details auf der Tkinter-Referenzseite nach.

Es gibt noch zwei andere Layout-Manager in Tkinter, bekannt als das grid (Gitter) und der placer (Plazierer). Um den Grid-Manager zu verwenden, musst du grid() anstatt pack() verwenden und für den Placer nimmst du place() anstatt pack().Jeder hat seinen eigenen Optionssatz und da ich in dieser Einführung nur den Packer behandle, solltest du dir das Tkinter-Tutorium und die Referenz für die Details anschauen. Die Hauptpunkte, die wir beachten müssen, ist, dass der grid- Manager die Komponenten innerhalb des Fensters in der Form eines Gitters anordnet (oh welch eine Überaschung!) - dies kann oftmals bei Dialogboxen sehr zweckmäßig sein, bei denen beispielsweise Zeilen mit Texteingabefeldern vorkommen. Der Plazierer hingegen verwendet feste Koordinaten in Pixel oder Relativkoordinaten innerhalb eines Fensters. Das Letztere erlaubt es den Komponenten sich entlang des Fensters mit zu vergrößern - sagen wir einmal es sollten immer 75% der Vertikalen besetzt sein. Dies ist sehr nützlich bei sehr komplizierten Fenstergestaltungen, aber es erfordert eine große Menge an Vorausplanung - hierzu empfehle ich unbedingt ein Blatt kariertes Papier, einen Bleistift und einen Radiergummi!

Kontrolle des Erscheinungsbildes mittels Frames und dem Packer

Das Frame-Widget hat nun einige nützliche Eigenschaften, die wir verwenden können. Vor allem ist es oft gut, wenn man eine logische Abgrenzung um bestimmte Komponenten hat, doch manchmal wollen wir etwas, das wir auch sehen können. Dies ist besonders nützlich bei gruppierten Contols, wie Radio-Buttons oder Check-Boxen. Das Frame löst dieses Problem , dadurch dass es gemeinsam mit vielen anderen Tk-Widgets mit einer Relief - Eigenschaft ausgestattet ist. Relief kann einen von mehereren Werten annehmen: sunken, raised, groove, ridge oder flat. Lass uns den sunken - Wert auf unsere einfache Dialogbox anwenden. Wir ändern einfach die Zeile, bei der das Frame erzeugt wird:

 F = Frame(top,relief="sunken", border=1) 

Anmerkung 1: Du musst ebenfalls einen Rand (border) hinzufügen. Tust du das nicht, wird das Frame zwar versenkt, aber mit einem unsichtbaren Rand - und du siehst keinen Unterschied!

Anmerkung 2: Schreibe die Randgröße nicht in Anführungszeichen. Dies ist eines der verwirrenden Aspekte der Tk-Programmierung, nämmlich zu wissen, wann man Anführungszeichen um eine Option setzt und wann man sie weglassen muss. Grunsätzlich wenn es ein nummerisches Zeichen oder ein einzelnes Chracter ist, kannst du die Anführungszeichen weglassen. Wenn es sich um eine Mischung aus Ziffern und Buchstaben oder um einen String handelt, dann brauchst du die Gänsefüßchen. Dabei ist es egal, ob die Buchstaben klein- oder großgeschrieben sind. Unglücklicherweise gibt es keine einfache Lösung, du kannst nur aus Erfahrung lernen - Python gibt eine Liste der gültigen Optionen in seinen Fehlermeldungen aus !

Eine andere bemerkenswerte Sache ist, dass das Frame nicht das Fenster ausfüllt. Dies können wir mit einer anderen Packe-Option festlegen, nämlich , das überascht nicht, durch fill. Wenn du das Frame packst, dann tu es so:

F.pack(fill="x")

Dies füllt horizontal, wenn du das komplette Frame ausfüllen möchtest, verwende ebenfalls fill='y'.

Das Endergebnis des ausführenden Skripts schaut nun so aus:

Sunken Frame

Mehr Widgets hinzufügen

Lass uns jetzt das Texteingabe-Widget,nämlich Entry, anschauen. Diese ist die vertraute einfache Zeile einer Texteingabebox. Sie verwendet viele der Methoden des viel weiter entwickelten Text - Widget, das wir hier nicht betrachten wollen. Ich hoffe, die verwendung der Methoden des Entry - Widget wird eine Verständnisgrundlage schaffen für ein späteres Experimentieren mit der Textbox.

Gehen wir zurück zu unserem "Hello World" - Programm und fügen wir ein Texteingabe-Widget mit einem eigenen Frame und einem Button ein, mit dem man den von uns eingegebenen Text wieder löschen kann. Dies wird nicht nur demonstrieren, wie man das Entry-Widget erzeugt und verwendet, sonden auch wie eigene Ereignisbehandlungsfunktionen definiert und mit den Widgets verbunden werden.

from Tkinter import *

# erzeuge zuerst eien Ereignisbehandler
def evClear():
  eHello.delete(0,END)

# erzeuge das Toplevel-Window/- Frame
top = Tk()
F = Frame(top)
F.pack(expand="true")

# jetzt das Frame mit dem Text-Entry
fEntry = Frame(F, border="1")
eHello = Entry(fEntry)
fEntry.pack(side="top", expand="true")
eHello.pack(side="left", expand="true")

# schließlich das Frame mit den Buttons. 
# wir versenken dieses zu Betonung
fButtons = Frame(F, relief="sunken", border=1)
bClear = Button(fButtons, text="Clear Text", command=evClear)
bClear.pack(side="left", padx=5, pady=2)
bQuit = Button(fButtons, text="Quit", command=F.quit)
bQuit.pack(side="left", padx=5, pady=2)
fButtons.pack(side="top", expand="true")

# jetzt kommt die Ereignisschleife
F.mainloop()

Beachte, dass wir noch einmal den Namen der Ereignisbehandlung (evClear) als command - Argument an den bClear -Button Übergeben. Beachte auch die verwendung einer Namenskonvention, evXXX um die Ereignisbehandlung mit dem zugehörigen Widget zu verbinden.

Das Laufenlassen des Pragrammes ergibt:

Entry and button controls

Und wenn du etwas in die Texteingabebox eintippst, dann betätige den "Clear Text" - Button, der alles wieder löscht.

Binden von Ereignissen - von Widgets zu Code

Bis hierher haben wir die Befehlseigenschaft (command) zur Verbindung von Python-Funktionen mit GUI-Ereignissen. Manchmal möchten wir eine explizitere Kontolle haben, z.B. eine bestimmte Tastenkombination abfangen. Der Weg sowas zu tun, ist die Verwendung der bind - Funktion um explizit ein Ereignis und eine Pythonfunktion aneinander zu hängen (oder zu binden).

Wir weden jetzt einen "HotKey" definieren - sagen wir einmal CTRL-C - um den Text im obigen Beispiel zu löschen. Um dies zu tun, müssen wir die CTR-C Tastenkombination an den gleichen ereignisbehandler binden, wie der Clear-Button. Unglücklicherweise gibt es hier einen unerwarteten Haken. Wenn wir die command-Option verwenden, so darf die spezifizierte Funktion keine Argumente bekommen. Wenn wir hingegen die bind-funktion verwenden, um zu veranlassen, dass die Funktion gebunden wird, dann benötigen wir ein Argument. deshalb müssen wir eine neue Funktion erzeugen mit nur einem einzigen Parameter, den wir hier evClear nennen. Füge folgendes an die evClear-Definition an:

def evHotKey(event):
    evClear()

Und trage folgende Zeile im Anschluss an die Definition des Entry-Widget ein:

eHello.bind("",evHotKey) # die Tasten-Definition ist abhängig von Groß- und Kleinschreibung 

Lass das Programm wieder laufen und du kannst jetzt den Text durch den button oder durch Eingabe von Ctrl-c löschen. Wir können auch bind verwenden, um verschiedene Dinge aufzufangen, wie Maus-Klicks oder ob etwas den Fokus erhält oder verliert oder sogar, ob ein fenster sichtbar wird. Schau in der Tkinter-Documentation nach mehr Information dazu nach. Der härteste Teil ist gewöhnlich die Formulierung der Ereignisbeschreibung!

Eine kurze Meldung

Du kannst deinem Anwender durch die Verwendung einer MessageBox kurze Nachrichten melden. Dies ist in Tk ganz einfach und wird unter Benutzung der tkMessageBox - Modul - Funktion ausgeführt, wie jetzt gezeigt wird:

import tkMessageBox
tkMessageBox.showinfo("Window Title", "A short message") 

Es gibt auch eine Fehlermeldungs, Warnungs-, Yes/No- und OK/Cancel - Boxen mittels unterschiedlicher showXXX - Funktionen. Sie sind mit unterschiedlichen Icons und Buttons ausgestattet. Die beiden letzten verwenden askXXX anstatt showXXX und geben einen Wert zurück, um anzuzeigen, welcher Button der Anwender gedrückt hat, nämlich so:

res = tMessageBox.askokcancel("Which?", "Ready to stop?")
print res

Hier sind einige der Tkinter Message-Boxen:

Info box   Error box   Yes/No box

 

Die Tcl - Perspektive

Da wir durch den ersten Teil dieses Tutoriums hindurch Python mit Tcl verglichen haben, scheint es jetzt naheliegend, zu zeigen, wie das frühere Label- und Button-Beispiel in der Originalform aussieht:

Label .lHello -text "Hello World"
Button .bHello -text Quit -command "exit"
wm title . Hello
pack .lHello .bHello

Wie du siehst ist es sehr einfach. Die Widget-Hierarchie wird wird durch eine Namenskonvention mit '.' formiert, wie das Top-Level-Widget. Wie in Tcl gebräuchlich, sind die Widgets Befehle, deren Eigenschaften als Argummente übergeben werden. Wie erhofft ist die Übersetzung der Widget-Parameter in die von Python verwendeten Argumente ganz eindeutig. Das bedeutet, dass du die Tcl/Tk-Dokumentation (die ziemlich umfangreich ist) als Hilfe heranziehen kannst, um Probleme bei der Tkinter-Programmierung zu lösen, es ist ledigliche eine einfache Übertragung.

Das ist es, inwieweit ich hier in Tcl/Tk vordringen möchte. Bevor wir das ganze beenden, werde ich aber noch eine übliche Technik zeigen, im Tkinter-GUI-Applikationen als Objekte zu bündeln.

Applikationen als Objekte verpacken

Es ist üblich bei der Programmierung eines GUI die gesamte Applikation als Klasse einzuhüllen. Dies fordert die Frage heraus: Wie können wir die Widgets einer Tkinter-Anwendung in diese Klassenstruktur einpassen? Es gibt zwei Möglichkeiten, entweder wir entscheiden uns, die Applikation selbst als eine Subklasse eines Tkinter-Frame zu machen oder wir haben ein Mitgliedsfeld, das eine Referenz zum Toplevel-Fenster speichert. Die letzte Möglichkeit ist die häufigste in anderen Toolkits, so dass wir diese auch hier verwenden werden. Wenn du die erste Möglichkeit in Aktion sehen willst, dann ghe zurück und schau dir das Beispiel zum Thema eignisgesteuerte Programmierung an. (Dieses Beispiel zeigt auch den grundlegenden Gebrauch des unglaublich vielseitigen Tkinter-Text-Widget)

Ich werde das obige Beispiel durch die Verwendung eines Eingabefelds, Lösch-Buttons und Quit-Buttons auf eine OO-Struktur abändern. Zuerst erzeugen wir eine Applikationsklasse, worin der Konstruktor die visuellen Teile des GUI aufnimmt.

Wir weisen das sich ergebende Frame self.mainWindow zu, was uns erlaubt, dass andere Methoden der Klasse auf das Top-Level-Frame zugreifen können. Andere Widgets, die wir verwenden möchten (so wie das Eingabefenster), werden genauso einer Mitgliedsvariable des Frame zugewiesen. Durch die Verwendung dieser Technik erhält die Ereignisbehandlung Methoden der Applikationsklasse und alle haben Zugriff auf jedes andere Datenmitglied der Anwendung (obwohl es in diesem Falle keine gibt) durch die self - Referenz. Dies erlaubt eine nahtlose Integration des GUI mit den zugrunde liegenden Applikationsobjekten:

from Tkinter import *
     
class ClearApp:
   def __init__(self, parent=0):
      self.mainWindow = Frame(parent)
      # erzeuge das Eingabefenster
      self.entry = Entry(self.mainWindow)
      self.entry.insert(0,"Hello world")
      self.entry.pack(fill=X)
      
      # füge jetzt 2 Buttons hinzu
      # wir verwenden ein ineinander eingefügtes Framepaar für den Vertiefungseffekt
      fOuter = Frame(self.mainWindow, border=1, relief="sunken")
      fButtons = Frame(fOuter, border=1, relief="raised")
      bClear = Button(fButtons, text="Clear", 
                      width=8, height=1, command=self.clearText)
      bQuit = Button(fButtons, text="Quit", 
                      width=8, height=1, command=self.mainWindow.quit)
      bClear.pack(side="left", padx=15, pady=1)
      bQuit.pack(side="right", padx=15, pady=1)
      fButtons.pack(fill=X)
      fOuter.pack(fill=X)
      self.mainWindow.pack()

      # festlegen des Titels
      self.mainWindow.master.title("Clear")
      
   def clearText(self):
      self.entry.delete(0,END)
      
app = ClearApp()
app.mainWindow.mainloop()

Hier ist das Resultat:

OOP version

das Ergebnis sieht auffallend ähnlich wie das vorher erzeugte aus, obwohl ich das untere Frame eingepasst habe, um ihm ein schön gefurchte Oberfläche zu geben, und ich habe die Buttons verbreitert, um sie dem unten aufgeführten wxPython-Beispiel ähnlicher zu machen.

Doch ist dies nicht die Hauptaufgabe, dass wir ein Objekt erstellen können. Wir könnten eine Klasse erzeugen, basierend auf einem Frame, dass einen Standardsatz Buttons enthält und diese Klasse wieder verwenden, sagen wir einmal zum Bau von Dialogfenstern. We könnten sogar ganze Dialoge erschaffen und diese in verschiedenen Projekten mit verwenden. Oder wir könnten die Möglichkeiten der Standard-Widgets durch Unterklassierung erweitern - vielleicht um Buttons in Abhängigkeit ihres Zustandes zu colorieren. Dies wurde mit den Python-Mega-Widgets (PMW) gemacht, die eine Erweiterung zu Tkinter sind und die du herunterladen kannst.

Eine Alternative - wxPython

Es sind viele andere GUI-Toolkits erhältlich, aber eines der populärsten ist das wxPython - Toolkit, das wiederum eine Abart des C++ - Toolkits wxWindows ist. wxPython ist allgemein ein viel typischeres GUI-Toolkit als Tkinter. Es ist außerdem schon von vorneherein mit einer größeren Standard-Funktionalität als Tk ausgestattet - Dinge wie Tooltips, Statuszeilen usw, die in Tkinter handgearbeitet sind. Wir werden wxWindows verwenden, um das simple "Hello World"-Label-und-Button-Beispiel von oben neu zu erzeugen.

Ich möchte dies nicht im Detail durchgehen, wenn du mehr über das Arbeiten mit wxPython wissen möchtest, dann solltest du das Paket von der wxPython-Webseite herunter laden.

Allgemein ausgedrückt definiert das Tollkit ein Gerüst, das es erlaubt, Windows zu erzeugen und diese mit Controls zu bevölkern und Methoden an diese Controls zu binden. Es ist vollständig objektorientiert, weshalb du eher Methoden statt Funktionen verwenden solltest. Das Beispiel sieht so aus:

from wxPython.wx import *

# --- Definiere ein Frame, das zum Hauptfenster wird ---
class HelloFrame(wxFrame):
   def __init__(self, parent, ID, title, pos, size):
        wxFrame.__init__(self, parent, ID, title, pos, size)
	# wie benötigen ein Panel um den richtigen Hintergrund zu erhalten
        panel = wxPanel(self, -1)

	# Jetzt erzeugen wir die Text- und Button-Widgets
	self.tHello = wxTextCtrl(panel, -1, "Hello world", (3,3), (185,22))
        button = wxButton(panel, 10, "Clear", (15, 32))
        button = wxButton(panel, 20, "Quit", (100, 32))

	# jetzt binden wir den Button an den "Handler"
        EVT_BUTTON(self, 10, self.OnClear)
        EVT_BUTTON(self, 20, self.OnQuit)
	
   # dies sind unsere Ereignisbehandlungen
   def OnClear(self, event):
       self.tHello.Clear()
       
   def OnQuit(self, event):
       self.Destroy()

# --- Definiere das Anwendungs-Objekt ---
# Beachte, dass alle wxPython programs eine Applikationsklasse 
# definieren müssen, die von wxApp abgeleitet wird.
class HelloApp(wxApp):
   def OnInit(self):
       frame = HelloFrame(NULL, -1, "Hello", (200,50),(200,90) )
       frame.Show(true)
       # self.setTopWindow(frame)
       return true

# erzeuge eine Instanz und starte die Ereignisschleife
HelloApp().MainLoop()

Und das sieht so aus:

wxPython Hello program

Zu beachtende Punkte sind, dass man Gebrauch von den Namenskonventionen für die Methoden machen sollte, die vom Framework aufgerufen werden - OnXXXX. Beachte auch, dass die EVT_XXX - Funktionen dazu dienen, Ereignisse an Widgets zu binden - davon gibt es eine ganze Familie. wxPython hat eine Unmenge an Widgets, sehr viel mehr als Tkinter, und mit diesen kannst du sehr ausgeklügelte GUIs basteln. Unglücklicherweise benützen diese ein Koordinaten-basiertes Plazierungsschema, das mit der zeit sehr ermüdend wird. Es gibt einen kommerziellen GUI-Builder und hoffentlich wird irgendjemand bald auch mal einen freien herausgeben.

Insbesondere mag es von Interesse sein, festzustellen, dass dieses und das ähnliche obige Tkinter-Beispiel beide exakt die gleiche Anzahl an Zeilen von ausführbarem Code besitzen - nämlich 21.

Als Folgerung lässt sich sagen, wenn du lediglich eine schnelle GUI-Oberfläche zu einem textbasierten Werkzeug benötigst, dann wird Tkinter deine Anforderungen mit gerinstem Aufwand erfüllen. Falls du aber eine voll ausgestattete plattformübergreifende GUI-Applikation erzeugen willst, dann solltest du dich näher mit wxPython befassen.

Andere Toolkits beinhalten MFC, pyGTk, pyQt, die beiden Letzten laufen gegenwärtig nur unter Linux, obwohl sie die Fähigkeit hätten, auch nach Windows portiert werden zu können. Viele der mit Tkinter durchgenommenen Lektionen lassen sich auch auf diese Toolkits anwenden, aber jedes hat seine eigenen Charakteristika und Schwächen. Nimm dir eins heraus, versuche es kennen zu lernen und geniesse die verrückte welt des GUI-Design.

Das ist für jetzt genug. Dies war nicht als Tkinter - Referenz - Seite gedacht, lediglich soviel, dass du damit anfangen kannst. Siehe den Tkinter-Abschnitt der Python-Webseiten an, um Links für andere Tkinter-Resourcen zu finden.

Es gibt auch mehrere Bücher über die Vwerwendung von Tcl/Tk und schließlich auch eins über Tkinter. Ich werde übrigens in der Fallstudie auf Tkinter zurückgreifen, wo ich eine Art der Einkapselung eines Matchmode-Programmes in ein GUI für eine anspruchsvollere Anwendung beleuchten werde.


Zurück Weiter Inhalt
 

Im falle von Fragen oder rückmeldungen zu dieser seite, sende bitte eine Mail in Englisch an den Autor alan.gauld@yahoo.co.uk oder auf Deutsch an den Übersetzer bup.schaefer@freenet.de