Wir wollen jetzt das Hauptfenster mit einer Menüzeile, einer Instanz von
ScrollableText und einer Leiste mit Radiobuttons erzeugen:
class Editor(Tkinter.Tk):
def __init__(self, initFile=None, *args,
**kwargs):
apply(Tkinter.Tk.__init__, (self,)+args,
kwargs)
self._generateMenu()
self.text = ScrollableText(self)
self.text.pack(expand=1, fill=Tkinter.BOTH)
self._generateEncodingFrame().pack(expand=1,
fill=Tkinter.X)
if initFile:
self.openFile(initFile)
Anmerkungen:
-
Wir erben hier von Tkinter.Tk. Das hat den Vorteil, dass wir Tk nicht anderweitig initialisieren müssen, aber den
Nachteil, dass wir nicht mehrere Fenster gleichzeitig aufmachen können. Würden wir von Tkinter.Toplevel erben,
hätten wir dieses Problem nicht (aber dafür andere, die nicht hierher gehören).
- Toplevel-Windows (also auch Tk)
können eine Menüzeile haben. Wir werden sie in der _generateMenu-Methode erzeugen.
- Den ScrollableText
packen wir so, dass er das Fenster so gut wie möglich ausfüllt.
- Wir werden noch einen zusätzlichen Frame erzeugen; da
die Buttons darin jedenfalls vertikal nicht sinnvoll wachsen können, sorgen wir beim Packen dafür, dass sie es auch
nicht tun.
- Schließlich erlauben wir noch, dass bereits beim Konstruieren unseres Anwendungsfensters eine Datei
geöffnet werden kann.
Die Radiobuttons erzeugen wir so:
def _generateEncodingFrame(self):
encodingFrame = Tkinter.Frame(self)
self.useEncoding = Tkinter.StringVar()
encodings = ["iso-8859-1", "iso-8859-2",
"iso-8859-3", "CP437", "UTF-8"]
for enc in encodings:
Tkinter.Radiobutton(encodingFrame,
text=enc, variable=self.useEncoding,
value=enc, command=self._changeEncoding
).pack(side=Tkinter.LEFT)
self.useEncoding.set(encodings[0])
self._changeEncoding()
return encodingFrame
Anmerkungen:
-
Es wäre denkbar gewesen, hier eine neue Klasse zu definieren. In der Tat wäre das sogar recht schlau, die Klasse
könnte dann Methoden wie getEncoding oder setEncoding haben und also das Verhalten “Wähle eine von
mehreren Optionen aus” sinnvoll verkapseln. Hier erzeugen wir stattdessen recht ad hoc einen Frame.
- Radiobuttons
sind GUI-Elemente, von denen immer nur einer zu einer Zeit “aktiv” (also ausgewählt) sein kann. Da wir immer nur
ein Encoding zu einer Zeit anzeigenk können, ist dies eine ganz brauchbare Metapher.
- Radiobuttons in Tkinter sind
immer an einer tcl-Variable gebunden, die die augenblickliche Auswahl reflektiert und die (cool Feature!)
Änderungen an sich auch an die Radiobuttons weitergibt. Diese tcl-Variablen bekommt man durch Aufruf von
Tkinter.StringVar. Wir werden diese Variable später brauchen und speichern daher eine Referenz auf sie in einer
Instanzvariablen.
- Wenn man die Radiobuttons konstruiert, muss man (a) das Elterwidget, (b) das Label, das hinter
dem Radiobutton steht, (c) die tcl-Variable, die von den Buttons gesteuert wird und (d) den Wert, den die
Variable annimmt, wenn der Radiobutton ausgewählt wird angeben.
- Ein Satz von Radiobuttons mit einer
gemeinsamen Variable heißt Gruppe, und es kann immer nur ein Radiobutton aus einer Gruppe selektiert sein –
daher auch der Name: Bei alten Radios gibt es Knöpfe, die genauso funktionieren: Wird einer gedrückt,
springen alle anderen raus.
- Eine tcl-Variable ist ganz anders als eine Python-Variable – ihr wird nicht
zugewiesen (in Python: Verbindung eines Namens mit einem Wert), ihre Werte werden mit einer Methode
set gesetzt (ein halbwegs brauchbares Analogon dafür in Python ist das Setzen eines Listenelements:
a[2] = 3 – a bleibt dabei in gewisser Weise unverändert, nur der an a gebundene Wert ändert sich).
Wir setzen auch für die Radiobuttons einen Callback, und zwar einen für alle. Wir müssen in diesem
Callback eigentlich nur dem Text-Widget Bescheid sagen, dass es sein Encoding ändern muss, etwa
so:
def _changeEncoding(self):
self.text.setEncoding(self.useEncoding.get())
Unser Menü machen wir so:
def _generateMenu(self):
menubar = Tkinter.Menu(self)
filemenu = Tkinter.Menu(menubar)
filemenu.add_command(label="Open...",
command=self.openFile)
filemenu.add_command(label="Save As...",
command=self.saveFile)
filemenu.add_separator()
filemenu.add_command(label="Quit",
command=self.quit)
menubar.add_cascade(label="File",
menu=filemenu)
specialmenu = Tkinter.Menu(menubar)
specialmenu.add_command(label="Permute",
command=self.permute)
menubar.add_cascade(label="Special",
menu=specialmenu)
self.config(menu=menubar)
Anmerkungen:
- Menu ist zunächst ein ganz normales Widget – wir könnten, so wir wollten, das auch Packen. Wir
haben aber hier andere Pläne.
- menubar wird später die Menüzeile werden, in die wir hier einfach zwei
Untermenüs einhängen, die wiederum Menu-Widgets sind.
- Um Menüpunkte in Menus unterzubringen, kann die
add command-Methode verwendet werden, die ein Label und einen Callback setzt. Die Methode add_separator fügt
einen Trennstrich ein, während die Methode add_cascade ein Untermenü einhängt. Letzteres wird hier
benutzt, um die Pull-down-Menüs in die Menüzeile einzuhängen, es ist aber auch erlaubt, auf diese Weise
Untermenüs in die Pull-Downs einzufügen. In neueren Style Guides wird aber von solchen Konstrukten in der
Regel abgeraten.
- Zum Konstruieren der Menüs braucht man also schon irgendwelche Callbacks. Ich
empfehle, die Funktionen, die das Programm später haben soll, bereits bei der Definition des Menüs
mitzudefinieren und in ihnen Exceptions auszulösen, die sagen, die entsprechend Funktion sei noch nicht
implementiert. Auf diese Weise kann man das Programm allmählich mit Funktionalität ausstatten, hat aber
trotzdem schon von Anfang an etwas, das funktioniert (sowas hieß eine Weile lang “rapid prototyping”).
- Schließlich haben Toplevel-Windows eine Option menu. Darüber kann man ihnen (im Gegensatz zu allen
anderen Widgets) eine Menüleiste geben, was in der letzten Zeile der Funktion geschieht.
- Zur Benennung
der Menüpunkte nur so viel, dass gängige Styleguides vorschreiben, dass ein Eintrag, dessen Aufruf auf
einen Dialog führt, mit drei Punkten enden soll, solche, die unmittelbar aktiv werden, ohne drei Punkte.