Countdown in einem Label

Fragen zu Tkinter.
Sam123
User
Beiträge: 35
Registriert: Freitag 20. November 2020, 12:25

Hallo zusammen,
ich hab das kleine Programm unten geschrieben. Und es funktioniert auch.

Code: Alles auswählen

import tkinter
import random


def start_wuerfeln():
    global running
    running = True
    wuerfeln()


def wuerfeln():
    global running
    if (running == True):
        zufallszahl_wuerfel_1 = random.randint(1, 6)
        label_wuerfel_1["text"] = " " + str(zufallszahl_wuerfel_1) + " "
        hauptfenster.after(500, wuerfeln)


def stop_wuerfeln():
    global running
    running = False


running = False
hauptfenster = tkinter.Tk()
label_wuerfel_1 = tkinter.Label(hauptfenster, text=" 0 ", bg="yellow", font="Arial 44", borderwidth=5, relief="groove")
label_wuerfel_1.pack()
tkinter.Button(hauptfenster, text="Start", command=start_wuerfeln).pack()
tkinter.Button(hauptfenster, text="Stop", command=stop_wuerfeln).pack()
hauptfenster.mainloop()

Nun wollte ich das Programm mit einer main()-Funktion versehen, s.u. Es läuft auch, aber irgendwie nicht richtig. Denn beim Stop-Drücken würfelt er trotzdem weiter. Weiss jemand, was der Fehler ist? (Ich weiss, globale Variablen usw sind böse. Alles nur zu Übungszwecken.).

Code: Alles auswählen

import tkinter
import random
from functools import partial


def start_wuerfeln(hauptfenster, label_wuerfel_1):
    running = True
    wuerfeln(running, hauptfenster, label_wuerfel_1)


def wuerfeln(running, hauptfenster, label_wuerfel_1):
    print(running)
    if (running == True):
        zufallszahl_wuerfel_1 = random.randint(1, 6)
        label_wuerfel_1["text"] = " " + str(zufallszahl_wuerfel_1) + " "
        hauptfenster.after(500, wuerfeln, running, hauptfenster, label_wuerfel_1)


def stop_wuerfeln(hauptfenster, label_wuerfel_1):
    running = False
    wuerfeln(running, hauptfenster, label_wuerfel_1)


def main():
    hauptfenster = tkinter.Tk()
    label_wuerfel_1 = tkinter.Label(hauptfenster, text=" 0 ", bg="yellow", font="Arial 44", borderwidth=5, relief="groove")
    label_wuerfel_1.pack()
    tkinter.Button(hauptfenster, text="Start", command=partial(start_wuerfeln, hauptfenster, label_wuerfel_1)).pack()
    tkinter.Button(hauptfenster, text="Stop", command=partial(stop_wuerfeln, hauptfenster, label_wuerfel_1)).pack()
    hauptfenster.mainloop()


if __name__ == '__main__':
    main()

Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

In `stop_wuerfeln()` rufst Du `wuerfeln()` auf was einfach nichts macht, weil `running` den Wert `False` hat. Dieser Aufruf hat keinen Einfluss auf die anderen Aufrufe, denn das `running` ist ein anderes. Das ist eine lokaler Name der nichts mit den anderen Aufrufen zu tun hat.

Du brauchst eine Klasse um Dir den Zustand zu merken. Auch nicht unbedingt als Flag, sondern besser als `None` oder die ID des nächsten Ereignisses das mit `after()` geplant wurde. Denn damit und `after_cancel()` kann man das abbrechen/wiederrufen.

Namen sollte man nicht nummerieren. Ist hier auch total überflüssig eine 1 an die Namen zu hängen. Es gibt ja nicht mal eine andere Zahl.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Dafür brauchst Du eine Klasse:

Code: Alles auswählen

import tkinter
import random


class Hauptfenster(tkinter.Tk):
    def __init__(self):
        tkinter.Tk.__init__(self)
        self.wuerfel_id = None
        self.wuerfel_wert = tkinter.IntVar(self, 0)
        tkinter.Label(self, textvariable=self.wuerfel_wert,
            bg="yellow", font="Arial 44", borderwidth=5,
            relief="groove").pack()
        tkinter.Button(self, text="Start", command=self.start_wuerfeln).pack()
        tkinter.Button(self, text="Stop", command=self.stop_wuerfeln).pack()

    def start_wuerfeln(self):
        self.wuerfel_id = self.after(0, self.wuerfeln)

    def stop_wuerfeln(self):
        if self.wuerfel_id is not None:
            self.after_cancel(self.wuerfel_id)
            self.wuerfel_id = None

    def wuerfeln(self):
        self.wuerfel_wert.set(random.randint(1, 6))
        self.wuerfel_id = self.after(500, self.wuerfeln)


def main():
    hauptfenster = Hauptfenster()
    hauptfenster.mainloop()


if __name__ == '__main__':
    main()
Sam123
User
Beiträge: 35
Registriert: Freitag 20. November 2020, 12:25

Vielen Dank für euere Antworten!
Ich bin jedoch erstmal eine Stufe runtergegangen und hab versucht, das einfache Beispiel für die Konsole zu schreiben s.u.
In der GUI kann ich eine Funktion immer wieder aufrufen während sie läuft. Hier funktioniert das nicht, die Funktion start_wuerfeln() blockiert dann das Programm und die weitere Ausführung. Gibt es eine Möglichkeit die Funktion zu unterbrechen, wenn ich z.B. eine betimmte Taste drücke und dann weiter laufen lassen mit einer anderen Taste?

Code: Alles auswählen

import random
from time import sleep


def start_wuerfeln(running):
    running = True
    wuerfeln(running)


def wuerfeln(running):
    if (running == True):
        zufallszahl = random.randint(1,6)
        print(zufallszahl)
        sleep(0.5)
        wuerfeln(running)


def stop_wuerfeln():
    running = False
    wuerfeln(running)


def main():
    running = False
    start_wuerfeln(running)
    sleep(5)
    stop_wuerfeln()


if __name__ == '__main__':
    main()
Benutzeravatar
sparrow
User
Beiträge: 4187
Registriert: Freitag 17. April 2009, 10:28

@Sam123: Vollzieh mal nach, was dein Programm tut.

In start_wuerfeln bindest du True an den Namen running. running übergibst du an wuefeln. In wuerfeln prruefst du, ob running wahr ist (ist es, wie wir gerade festgetellt haben), dann gibst du eine Zufallszahl aus, legst das Programm eine halbe Sekunde schlafen und rufst dann wieder wuerfeln auf.
Das erneute Aufrufen von wuerfeln wäre auch in einer GUI ein falscher rekursiver Aufruf. Warum sollte sich wuerfeln selbst aufrufen?
Und da auch bei dem rekursiven Aufruf die Umstände nicht geändert haben, kommst du wieder zu der Bedingung, ob running wahr ist ... und da das wahr ist geht es wieder den selben Gang.

Vielleicht kannst du kurz beschreiben, was genau du erreichen willst, dann kann man dir auch einen möglichen aufbau an die Hand geben. Dein Versuch lässt da viele Interpretationsmöglichkeiten.
Sam123
User
Beiträge: 35
Registriert: Freitag 20. November 2020, 12:25

ich denke, auf dem Level ist mir schon klar, was das Programm macht. Warum eigentlich falscher rekursiver Ausdruck? In den Beispielen davor (z.B. vom 3. Februar, s.u.) hat sich die Funktion doch auch immer wieder selbst aufgerufen, mit after. Ist es keine Rekursion?

Code: Alles auswählen

def wuerfeln(self):
        self.wuerfel_wert.set(random.randint(1, 6))
        self.wuerfel_id = self.after(500, self.wuerfeln)

Wenn ich das mit einer while-Schleife mache, dann habe ich keine Rekursion. Aber kann ich die Funktion wuerfeln() irgendwie stoppen, wenn sie erstmal läuft, running = False übergeben? Und dann evtl. wieder starten, wie bei der GUI mit den Buttons "Start" und "Stop".
Ich will später ein umfangreicheres Würfelspiel programmieren. Aber jetzt versuche ich erstmal, Python ein bischen besser zu vestehen :)

Code: Alles auswählen

import random
from time import sleep


def start_wuerfeln(running):
    running = True
    wuerfeln(running)


def wuerfeln(running):
    while(running == True):
        zufallszahl = random.randint(1,6)
        print("*****")
        print("* " + str(zufallszahl)  +" *")
        print("*****\n")
        sleep(0.5)


def stop_wuerfeln():
    running = False
    wuerfeln(running)


def main():
    running = False
    start_wuerfeln(running)
    stop_wuerfeln()


if __name__ == '__main__':
    main()
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Nein, das ist keine Rekursion, die Ausführung wird immer wieder an den Mainloop zurückgegeben.
Ein Konsolenprogramm sieht normalerweise keine solche Interaktion vor. Da braucht man dann wieder Zusatzpakete für Text-UIs und dann bist Du bei einer GUI besser aufgehoben.
Antworten