Dialoge & Popup-Menüs


Ein Popup-Menü öffnet sich, wenn man beispielsweise in einem Text-Widget auf die rechte oder linke Maustaste drückt. Wichtig: Das Menü öffnet sich gerade an der Stelle, an der sich der Mauszeiger befindet. Dies bedeutet: Dem Popup-Menü muss irgendwie mitgeteilt werden, an welcher Position es sich öffnen soll. Ein Beispiel (siehe popup.py):

from Tkinter import *            ## Tkinter importieren

## Eventhandler, liefert Maus-Koordinaten
def popupMenu(event):
    popup.post(event.x_root, event.y_root)

root = Tk()        ## Toplevel-Widget

foben = Frame(root,width=500)         ## Frame
foben.pack(expand=YES, fill=BOTH)

textfenster = Text(foben,width=90)        ## Textfenster
textfenster.pack(fill=BOTH,expand=YES)

popup = Menu(foben,tearoff=0)        ## Popup-Menue
popup.add_command(label='Info', command=info)
popup.add_separator()
popup.add_command(label='Beenden', command=ende)

## Maustaste an Event-Methode binden
textfenster.bind('<Button-3>',popupMenu)

Ergebnis:

Das Popup-Menü wird wie ein ganz normales Menü vereinbart: popup = Menu(foben,tearoff=0) Dabei ist foben der Rahmen (allgemein: das Widget), dem das Menü zugeordnet ist (anschaulich: das Widget, in dem das Menü erscheint). Der Paramater tearoff bestimmt darüber, ob das Menü ein sogenanntes abreißbares Menü (tearoff=1) ist oder nicht (tearoff=0). Ein abreißbares Menü kann von seiner momentanen Position "abgerissen" werden, es liegt dann als selbstandiges Tk-Fenster auf dem Desktop! Diese Voreinstellung ist bei Popup-Menüs nicht sinnvoll. Mit der Methode add_command() kann ein Eintrag zum Menü hinzugefügt werden. Die Eigenschaft label legt die Beschriftung des Menü-Eintrags fest, die zugeordnete Aktion wird über command bestimmt. für die Trennungslinie zwischen den Einträgen ist die Methode add_separator() verantwortlich. Die rechte Maustaste wird an die Methode popupMenü(event) gebunden, sie liefert die Mauskoordinaten zurück, die zur Anzeige des Popup-Menüs an der betreffenden Stelle benötigt werden.


Dialoge

Dialog-Fenster sind überraschend schwer zu programmieren! Warum ist das so? Erstens hat man es mit zwei Fenstern zu tun, wobei das Schließen des Dialog-Fensters nicht gleich die ganze Tkinter-Anwendung beenden darf. Zweitens findet normalerweise irgendein Datenaustausch zwischen dem Dialog-Fenster und dem Tkinter-Skript statt, man braucht also (Tkinter-)Variablen, die dann nach Beenden des Dialogs ausgelesen werden. Beispiel:

Dieser Dialog wird aufgerufen im Skript dialog.py. Wir betreten hier endgültig das weite Feld der objektorientierten Programmierung: Fenster und Buttons sind eben Objekte mit bestimmten Eigenschaften und Verhaltensweisen, sprich Methoden. Man fasst das Verhalten dieser Objekte in Klassen zusammen, und eine konkrete Instanz dieser Klasse ergibt dann ein Objekt (dieser Klasse). Beispiel: Es gibt die Text-Widget-Klasse, und ein konkretes Textfenster vereinbaren wir so:

textfenster = Text()

textfenster ist hier der Name des Objektes, über den wir Zugriff auf die Eigenschaften und Methoden der Klasse Text() haben. Nichts anderes haben wir oben gemacht. Mit Klassen ist meist ein Modell der Vererbung verbunden: das heisst, dass Klassen Eigenschaften und Methoden vererben können, die man dann in den Subklassen verändern (man sagt auch: überschreiben) oder ergänzen kann. Klassen bieten damit ein sehr flexibles Konzept zur Erweiterung der Fensterbibliothek! Im Fall der Dialogfenster machen wir es genau so, wie Fredrik Lundh in seiner Einführung in Tkinter: F. Lundh hat das Modul tkSimpleDialog mit der Klasse Dialog geschrieben, dieses Modul gehört zum Standardumfang von Python's Tkinter Bibliothek. Wir überschreiben in unserer Anwendung die folgenden beiden Methoden der Dialog-Klasse:

body(self, master)

apply(self)

Der Parameter master bezeichnet beim Aufruf das Objekt, dem unser Dialogfenster zugeordnet ist. In unseren Beispielen war das entweder root oder ein Frame-Widget. Der Parameter self hat eine besondere Bedeutung: Wenn ich eine Instanz einer Klasse gebildet habe und auf die Eigenschaften des Objektes zugreifen möchte, so wird self durch den Objektnamen ersetzt. Mit anderen Worten: self bezeichnet die konkrete Instanz einer Klasse, also das Objekt, das ins "Leben" gerufen wurde; im Beispiel oben war dies textfenster. In der body-Methode bestimmen wir das Aussehen des Dialogfensters, also ob es ein Label oder eine Eingabezeile oder beispielsweise Checkbuttons haben soll. Die Methode apply wird beim Klick auf den Ok-Button ausgeführt, sie sorgt im Beispiel für das Auslesen der Eingabezeile und speichert deren Inhalt in einer Variablen. Außerdem wird in der Methode eine Flag-Variable result gesetzt, die anzeigt: alles ist gut gegangen! Hier die Klasse DialogFenster, abgeleitet aus der Klasse Dialog (→ dialog.py):

import tkSimpleDialog
class DialogFenster(tkSimpleDialog.Dialog):
    ## so leitet man in Python aus einer Klasse ab!
    ## Aufruf: NamenDialog = DialogFenster(root)
    def body(self, master):  ## wird ueberschrieben
        self.title('Login?')

        self.namen = Label(master, text='Name:  ')
        self.namen.pack(side=LEFT)

        self.e1 = Entry(master,width=18)
        self.e1.insert('0','Tommy')

        self.e1.pack(side=LEFT)

        return self.e1 # Fokus auf die Eingabezeile setzen


    def apply(self):  ## wird ueberschrieben
        self.a1 = self.e1.get()
        self.result = 1 ## alles ok!

Die Klassendefinition DialogFenster "steht", wie aber wird sie benutzt? Dazu muss ein Objekt ins "Leben" gerufen werden. Im Skript dialog.py gibt es hierzu die Methode benutzer(), die als Aktion einem Eintrag im Popup-Menü zugeordnet ist (command=benutzer). In der Methode benutzer() wird zuerst unser Dialogfenster ins "Leben" gerufen, dann wird die Flag-Variable result getestet, und schließlich wird der Inhalt der Eingabezeile des Dialogfensters im Textfenster (& in einer Message-Box) ausgegeben:

def benutzer():
    NamenDialog = DialogFenster(root)
    if NamenDialog.result <> None:
        textfenster.insert(END,'Hallo, ' + NamenDialog.a1 + '!\n')
        tkMessageBox.showinfo('Dialog-Fenster','Hallo, ' + NamenDialog.a1 + '!')

Aufgaben

  1. Ergänze das Popup-Menü im Tkinter-Skript popup.py um einen weiteren Eintrag mit entsprechender callback-Methode (siehe den Abschnitt über's Binding).

  2. In das Skript popup.py ist eine Python-Test-Version eingebaut, sie testet um welche Python-Version es sich handelt. Wo befindet sich dieser Programm-Teil, und weshalb ist er notwendig?

  3. Jetzt bauen wir unser eigenes Dialog-Fenster: Erweitere dein Skript aus Aufgabe 1 um die notwendigen Teile für ein Dialogfenster mit zwei Labels (!) und zwei Eingabezeilen (!!) (Label 1: 'Login', Label 2: 'Password'). Wo mussen diese Änderungen hinein? Wie kann man die Eingabe in der zweiten Eingabezeile auslesen? Und wie kann man die Eingabe aus dem Dialog-Fenster im Text-Widget anzeigen?
    Bei Fragen untersuche das Skript dialog.py (Dieses Skript enthält keine Lösung der Aufgabe, du kannst aber im Chat-Client nachschauen: im Skript sar_client.py gibt es die Methode einstellungen(), die ein Fenster für die Einstellungen öffnet, beispielsweise für die Einstellung der TCP/IP-Adresse des Servers)


→ sp, 2016-12-02