Wo liegt hier mein Denkfehler?

Fragen zu Tkinter.
Antworten
skylake87
User
Beiträge: 7
Registriert: Sonntag 24. Januar 2021, 13:46

Hi,

wenn ich folgendes Programm starte erscheint die Fehlermeldung, dass der Name "btntest" noch nicht definiert ist. Das ist mir auch klar, da die Variable erst in Zeile 19 definiert wird. Wenn ich allerdings die Funktion unterhalb von Zeile 19 einfüge meckert der Compiler, dass in Zeile 19 die Funktion wiederum nicht bekannt ist.

Jetzt stehe ich total auf dem Schlauch. Wie kann ich das Problem lösen?

Code: Alles auswählen

import tkinter

change = True


def helloCallback(change):
    if change is True:
        btntest.place(x=25, y=100)
        change = False
    else:
        btntest.place(x=30, y=50)
        change = True


form = tkinter.Tk()
form.title("Das erste GUI Programm")
form.wm_geometry("400x200")

btntest = tkinter.Button(form, text="Hello", command=helloCallback(change))
btntest.pack()

form.mainloop()
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Da sind eine ganze Reihe von Denkfehlern drin.

Es faengt damit an, dass man True und Fall nicht mit "is" vergleicht. Das ist ein Test auf Objektidentitaet, und eine Zahl oder ein Boolscher Wert hat nicht garantiert die gleiche Identitaet. Das funktioniert, weil der Python Intepreter eine Reihe von kleinen Zahlen "cached", aber darauf sollte man sich nicht verlassen.

Dann vergleicht man auch nicht mit der literalen boolschen Werten. Denn das Ergebnis ist ja auch wieder ein booolscher Wert. Warum sollte man dann aufhoeren?

Code: Alles auswählen

if ergebnis == True == True == True == ....
Stattdessen fragt man einfach ab, ob change ist, oder nicht:

Code: Alles auswählen

if change:
   ...
# negiert
if not change:
   ....
Dann ist change eine globale Variable. Immer eine ganz schlechte Idee. Man benutzt keine globalen Variablen. Wenn man in GUIs Zustand hat, dann wird es Zeit fuer objektorientierte Programmierung.

Man ruft eine Funktion so auf:

Code: Alles auswählen

funktionsname(argument)
Dann wird die sofort ausgefuehrt. So wie zB

Code: Alles auswählen

print("hallo")
Und du rufst

Code: Alles auswählen

helloCallback(change)
auf. Das wird also *sofort* ausgefuehrt.

Du musst die aber nicht aufrufen, sondern ein callback uebergeben. Also etwas aufrufbares, das *dann* aufgerufen wird, wenn der Button betaetigt wird.

Die Nutzung von place ist auch grosser Mist, weil das auf fast jedem Computer anders aussieht. Vor allem aber das dann auch noch zu mischen mit pack geht vollends in die Hose.
Und UI-Element dynamisch anzulegen (ausser in groeseren Einheiten, wie zB einem Dialog oder so), ist auch so gut wie nie eine gute Idee.

Das Programm ist ja ziemlich wertfrei, darum ist es auch nicht so richtig sinnvoll zu reparieren. Etwas, das eine gewisse Aehnlichkeit hat:

Code: Alles auswählen

import tkinter


class App:

    def __init__(self, root):
        btntest = tkinter.Button(root, text="Hello", command=self._toggle)  # <---- hier KEINE KLAMMERN! Das darf KEIN Aufruf sein!
        btntest.pack()
        self._change = False

    def _toggle(self):
        self._change = not self._change
        print(self._change)


def main():
    root = tkinter.Tk()
    root.title("Das erste GUI Programm")
    app = App(root)
    root.mainloop()


if __name__ == '__main__':
    main()

skylake87
User
Beiträge: 7
Registriert: Sonntag 24. Januar 2021, 13:46

Vielen Dank für die schnelle und ausführliche Antwort.

Ich habe das Programm jetzt erstmal wie folgt abgeändert:

Code: Alles auswählen

import tkinter

change = True


def helloCallback():
    global change
    
    if change:
        btntest.place(x=25, y=100)
        change = False
    else:
        btntest.place(x=30, y=50)
        change = True


form = tkinter.Tk()
form.title("Das erste GUI Programm")
form.wm_geometry("400x200")

btntest = tkinter.Button(form, text="Hello", command=helloCallback)
btntest.pack()

form.mainloop()
Es funktioniert jetzt auch so wie angedacht. Allerdings musst ich die Variable change innerhalb der Funktion helloCallback zu global ändern. Wie kann ich denn in meinem Programm (auch wenn es nicht "gut" ist) es so abändern, dass ich das global Keyword nicht benötige?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Habe ich dir doch gezeigt 🤔
skylake87
User
Beiträge: 7
Registriert: Sonntag 24. Januar 2021, 13:46

__deets__ hat geschrieben: Freitag 16. Juli 2021, 16:55 Habe ich dir doch gezeigt 🤔
In deinem Programm kann ich auf btntest nicht innerhalb der toggle Methode zugreifen. Ich möchte, dass der Button immer an einer neuen Stelle erscheint, aber ich verstehe nicht, wie ich darauf zugreifen soll so wie ich mir das vorstelle.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

In meinem Programm gibt es doch eine Variable, die zeigt, wie man Zustand zwischen App und dem callback ohne global teilt. Was unterscheidet die denn von dem Button?
skylake87
User
Beiträge: 7
Registriert: Sonntag 24. Januar 2021, 13:46

__deets__ hat geschrieben: Freitag 16. Juli 2021, 17:06 In meinem Programm gibt es doch eine Variable, die zeigt, wie man Zustand zwischen App und dem callback ohne global teilt. Was unterscheidet die denn von dem Button?
Wie soll ich den btntest in deinem Programm umsetzen?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Hast du mal probiert, meine Frage zu beantworten?

Wenn du die Grundlagen der Objektorientierung noch nicht beherrschst, gibt es dafür auch gute Tutorials.
skylake87
User
Beiträge: 7
Registriert: Sonntag 24. Januar 2021, 13:46

__deets__ hat geschrieben: Freitag 16. Juli 2021, 17:14 Hast du mal probiert, meine Frage zu beantworten?

Wenn du die Grundlagen der Objektorientierung noch nicht beherrschst, gibt es dafür auch gute Tutorials.
Sind die Antworten mit Absicht so formuliert, dass man andauernd erneut nachfragen muss oder gibt es hierfür Gründe?

ich schreibe doch ganz klar, dass btntest von dir im Konstruktor deklariert und initialisiert wird, ich auf diesen btntest aber scheinbar nicht via der Methode _toggle darauf zugreifen kann. Denn dann könnte ich die Position von dem Button ändern.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wenn es Absicht ist, dann ist das ja ein Grund, oder nicht? Und der Grund ist, dass du nachdenken sollst, und eine Transferleistung erbringen. Man muss halt mal nachdenken, statt sich das vorkauen lassen zu wollen. Was deshalb sinnvoll ist, weil nicht jedes mal wer anders dir deinen Code schreibt, wenn du eine Frage hast.

Und ich hab's schon gesagt, aber wiederhole es gerne: das von dir gewuenschte Verhalten wird am Beispiel von change schon vorexerziert. Das wird im Konstruktor angelegt, aber irgendwie schaffe ich es durch schwarze Magie (ca 5 Zeichen lang), das *trotzdem* auch im Callback zu benutzen. Ist das tatsaechlich so unmoeglich sich damit auseinander zu setzen, und das nachzuziehen?
skylake87
User
Beiträge: 7
Registriert: Sonntag 24. Januar 2021, 13:46

__deets__ hat geschrieben: Freitag 16. Juli 2021, 17:28 Wenn es Absicht ist, dann ist das ja ein Grund, oder nicht? Und der Grund ist, dass du nachdenken sollst, und eine Transferleistung erbringen. Man muss halt mal nachdenken, statt sich das vorkauen lassen zu wollen. Was deshalb sinnvoll ist, weil nicht jedes mal wer anders dir deinen Code schreibt, wenn du eine Frage hast.

Und ich hab's schon gesagt, aber wiederhole es gerne: das von dir gewuenschte Verhalten wird am Beispiel von change schon vorexerziert. Das wird im Konstruktor angelegt, aber irgendwie schaffe ich es durch schwarze Magie (ca 5 Zeichen lang), das *trotzdem* auch im Callback zu benutzen. Ist das tatsaechlich so unmoeglich sich damit auseinander zu setzen, und das nachzuziehen?
Mir ging es in meinem Ausgangspost nicht darum, einen fertigen Code zu bekommen. Das Programm so wie ich es wollte läuft ja jetzt. Nachdem du die OOP-Variante gepostet hast, wirft es bei mir die Frage auf, warum ich auf _change in _toggle zugreifen kann, aber nicht auf btntest, obwohl beides im Konstruktor gesetzt wird. Irgendwo liegt hier mein Denkfehler und ich bitte seit mehreren Posts darum, mir einfach zu erläutern wo dieser liegt.

Ich war der Meinung ein Forum ist dazu da, Fragen zu beantworten. Deine halbgaren, nicht helfen wollenden Aussagen lassen für mich nur den Schluss zu, dass du vermutlich einfach nur deine Beitragszahlen erhöhen möchtest oder es irgendwie witzig findest. Arme Seele.

Würde ich so mit meinen Kunden umgehen, wäre ich direkt arbeitslos.

Thread kann geschlossen werden. Ich frage wo anders nach.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du bist nicht so besonders gut im ziehen von Vergleichen. Oder allgemein im logischen Denken. Deine Unfähigkeit jetzt auf andere zu projizieren, und persönliche Angriffe zu fahren, verbessert das nicht.. Aber ist wenig überraschend bei jemandem, der offensichtlich eine hohe Anspruchshaltung hat, ohne das die irgendwie durch eigene Leistung gerechtfertigt wäre.

Denn hier liegt ein großes Missverständniss vor: du bist kein Kunde von mir. Die bezahlen 800€ am Tag, und für das Geld erkläre ich denen auch mit Engelsgeduld zum hundertsten Mal, wozu in Python das self in der OO da ist.

Aber du hast hier genau 0,nix gezahlt. Und dafür bekommst du Hinweise darauf, wie DU dir deine Lösung erarbeiten kannst. Code Beispiele inklusive. Alles umsonst. Babysitten ist aber nicht Teil des Deals.
Antworten