Tkinter Inhalte wieder löschen

Fragen zu Tkinter.
Antworten
Neu111
User
Beiträge: 69
Registriert: Dienstag 10. März 2020, 19:02

Hallo!
Folgende Ausgangslage:

Code: Alles auswählen

def showReihe2(event):
	bu1 = Button(master, width=13, text='Schaltfläche 1', command=lambda:berechnung('variante1'))
	bu1.grid(row=2, column=0, sticky=W, padx=1)
	bu2 = Button(master, width=13, text='Schaltfläche 2', command=lambda:berechnung('variante2'))
	bu2.grid(row=2, column=1, sticky=W, padx=1)
	
def loescheReihe2(event):
	bu1.destroy()
	bu2.destroy()

master.bind('g', showReihe2)
master.bind('b', loescheReihe2)
Drücke ich nun die Taste "g", so wird mir in der grafischen Oberfläche eine neue Zeile (row=2) mit den 2 Buttons eingeblendet.

Das funktioniert auch.

Nun möchte ich durch einen weiteren Tastaturbefehl (nochmaliger Tastendruck 'g' oder wie in meinem Beispiel die Taste 'b') die komplette Reihe 2 (row=2) wieder entfernen.

Dies funktioniert NICHT, weil in der zweiten Funktion "loescheReihe2" die Variable bu1 und bu2 nicht bekannt/definiert sind.

Wie kann ich das Problem lösen?
Sirius3
User
Beiträge: 18289
Registriert: Sonntag 21. Oktober 2012, 17:20

`bu1` und `bu2` sind lokale Variablen, die nur innerhalb von showReihe2 existieren. Ebenso sollte `master` eine lokale Variable sein, die als Argument showReihe2 übergeben wird.
Um das zu Ändern, muß man sich den Zustand (also die Knöpfe) über die Dauer des Funktionsaufrufst merken. Das macht man mit Klassendefinitionen. Jedes nicht-triviale GUI-Programm braucht das.
Benutze keine Abkürzungen, wenn Du schaltflaeche meinst, dann schreibe nicht bu. Besser wäre es noch, die Schaltfläche nach ihrer Bedeutung zu benennen. Funktionsnamen schreibt man wie Variablennamen klein_mit_unterstruch, also loesche_reihe2, eingerückt wird immer mit 4 Leerzeichen pro Ebene, keine Tabs und die Namen aus Tkinter werden üblicherweise per tk.Button angesprochen, mit dem entsprechenden Import `import tkinter as tk`.

Das Grundgerüst eines GUI-Programms sieht eigentlich immer ähnlich aus:

Code: Alles auswählen

import tkinter as tk
from functools import partial

class MasterWindow(tk.Tk):
    def __init__(self):
        super().__init__()
        self.schaltflaeche1 = None
        self.schaltflaeche2 = None
        self.bind('g', self.show_reihe2)
        self.bind('b', self.loesche_reihe2)
        
    def show_reihe2(self, event):
        if self.schaltflaeche1 is not None:
            # Schaltflaechen schon erzeugt
            return
        self.schaltflaeche1 = tk.Button(master, width=13, text='Schaltfläche 1', command=partial(self.berechnung, 'variante1'))
        self.schaltflaeche1.grid(row=2, column=0, sticky=tk.W, padx=1)
        self.schaltflaeche2 = tk.Button(master, width=13, text='Schaltfläche 1', command=partial(self.berechnung, 'variante2'))
        self.schaltflaeche2.grid(row=2, column=1, sticky=tk.W, padx=1)
	
    def loesche_reihe2(self, event):
        if self.schaltflaeche1 is None:
            # keine Schaltflaeche da
            return
        self.schaltflaeche1.destroy()
        self.schaltflaeche2.destroy()
        self.schaltflaeche1 = None
        self.schaltflaeche2 = None

    def berechnung(self, variante):
        pass

def main():
    master = MasterWindow()
    master.mainloop()
    
if __name__ == '__main__':
    main()
Man versucht eigentlich, zu vermeiden, im nachhinein ein Fenster zu verändern. Üblich ist es, die Knöpfe schon in __init__ zu erzeugen und nur bei Bedarf anzuzeigen, bzw. aktiv zu schalten.
Neu111
User
Beiträge: 69
Registriert: Dienstag 10. März 2020, 19:02

Wow!
Mit so einer detaillierten Antwort hätte ich nicht gerechnet.
Vielen vielen DANK an Sirius3!!!!

Der Code funktioniert wunderbar, ich musste nur eine kleine Korrektur vornehmen (..tk.Button(master... durch tk.Button(self ... ersetzt)


Abschließend hätte ich noch ein paar Verständnisfragen:

1.) was ist der Unterschied zwischen

Code: Alles auswählen

import tkinter as tk
und

Code: Alles auswählen

from tkinter import *
2.) was ist der Unterschied zwischen

Code: Alles auswählen

command=partial
und

Code: Alles auswählen

command=lambda
3.) Wenn eine Funktion definiert wird, dann steht innerhalb der Runden Klammer zunächst ein "self":

Code: Alles auswählen

def show_reihe2(self, event):
Was bewirkt das "self" innerhalb dieser runden Klammer?

4) Warum keine Tabs beim einrücken?
...eingerückt wird immer mit 4 Leerzeichen pro Ebene, keine Tabs ...
5) Diese Zeile verstehe ich überhaupt nicht:

Code: Alles auswählen

if __name__ == '__main__':
Kann mir die Codezeile jemand etwas ausführlicher erklären?

PS: noch einmal recht herzlichen Dank an Sirius3 für seinen letzten Post!
Sirius3
User
Beiträge: 18289
Registriert: Sonntag 21. Oktober 2012, 17:20

Mit ›from tkinter import *‹ wird jeder Name der im Modul auch in den eigenen Namensraum geladen. Du hast also keine Kontrolle darüber, welche Namen das sind und es könnten welche, die Du selbst definiert hast, überschrieben werden. Bei ›import tkinter as tk‹ hat man die klare Trennung und kann kontrolliert per tk.xyz auf die Namen zugreifen.
Mit `partial` erzeugt man eine Funktion mit vordefinierten Parametern, bei lambda wird eine anonyme Funktion ohne eigenen Namensraum definiert, was zu seltsamen Fehlern führen kann.
`self` ist ein ganz normales Argument der Methode, wird aber automatisch mit der Instanz, die die Methode aufruft, belegt.
Tabs sind je nach Editor-Einstellung unterschiedlich groß, der Quellcode sieht also je nachdem unterschiedlich aus. Bei Leerzeichen kann das nicht passieren.

__name__ ist eine automatisch definierte Variable, die den Namen des Moduls enthält. Bei Skripten enthält sie den Wert "__main__". Damit kann man Module schreiben, die sich anders verhalten, wenn sie Importiert werden, wie wenn sie als Skript gestartet werden. Damit lassen sich Skripte auch als Module verwenden, was unter anderem bei multiprocessing, automatischer Dokumentation oder Unit-Tests benutzt wird.
Neu111
User
Beiträge: 69
Registriert: Dienstag 10. März 2020, 19:02

@Sirius3:
nochmal zu deinem Code oben:

Code: Alles auswählen

self.bind('g', self.show_reihe2)
self.bind('b', self.loesche_reihe2)
Gibt es eine Möglichkeit, die Aktion "self.show_reihe2" und "self.loesche_reihe2" mit ein- und derselben Taste auszuführen?

Sprich:
Taste g wird zum ersten Mal gedrückt --> "show_reihe2"
Taste g wird zum zweiten Mal gedrückt --> "loesche_reihe2"
Taste g wird zum dritten Mal gedrückt --> "show_reihe2"
usw.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Natuerlich, dann musst du dir nur merken, das du schonmal was gedrueckt hast, und entsprechend was anderes machen. Aber natuerlich in ein und derselben Rueckruffunktion.
Antworten