67. Ein fast nützliches Programm II

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] = 3a 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.


Markus Demleitner

Copyright Notice