Schleife mit Tk.Button beantworten

Fragen zu Tkinter.
Antworten
legion333
User
Beiträge: 7
Registriert: Montag 9. September 2024, 10:26
Wohnort: Berliner Umland

Hallo Pythonfreunde...

ich bin dranne ein kleines Spiel für meine Tochter zu programieren...

Dieses soll zunächst die ganz Zahlen von 0-10 per Zufall ausgeben und meine Tochter fragen od diese gerade, ungerade (und ein zusatz mit der Null) abfragen... Soweit so Gut wie man es nimmt... Nun dachte ich man könnte doch ihr, sage ich mal, 5 sec. geben und sie soll per Klicken eines Knopfes die Antwort geben... Die Zeit ist eher n kleines Problem, sowie die Fragen im label auszugeben... Nur wie Antwortet man per Knopf während einer Schleife? Habe es zunächst mit Threading versucht aber kläglich gescheitert. Am anfang habe ich 3 py's erstellt und diese eingebunden, wegen der Datengröße... ich weiß hier unnötig, aber für die Zukunft eventuell Nutzbar... Ja soweit bin ich schon, dass Importe mit * vermieden werden sollen und ich Danke "__JackBlack__" sowie "Sirius3" für die Top begründungen im Forum!

Der Code:

Code: Alles auswählen

from tkinter import *
from random import choice
import threading


import Zahlen
# zahl = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
import Uebung1
# zahl_u = ['1', '3', '5', '7', '9', '11', '13', '15', '17', '19']
import Uebung2
# zahl_n = ['0']
# zahl_g = ['2', '4', '6', '8', '10', '12', '14', '16', '18', '20']


should_run = False
class a:
    def __init__(self):
        while True:
            if should_run:
                for i in range(10):

                    if should_run:

                        a = choice(Zahlen.zahl)
                        b = Uebung1.zahl_u
                        c = Uebung2.zahl_n
                        d = Uebung2.zahl_g

                        print(a)

                        if a in b:
                            antw = input('g oder u: ')
                            if antw == 'u':
                                print('Richtig!'+'\n')
                            elif antw != 'u':
                                print('Falsch!'+'\n')
                            print('ungerade!'+'\n')

                        elif a in c:
                            antw = input('g oder u oder n: ')
                            if antw == 'n':
                                print('Richtig!'+'\n')
                            elif antw != 'n':
                                print('Falsch!'+'\n')
                            print('null!'+'\n')

                        elif a in d:
                            antw = input('g oder u: ')
                            if antw == 'g':
                                print('Richtig!'+'\n')
                            elif antw != 'g':
                                print('Falsch!'+'\n')
                            print('gerade!'+'\n')

                    if not should_run:
                        print('Stopped...')
                        break

t1 = threading.Thread(daemon = True, target = a)
t1.start()


class gui:

    def __init__(self, window):

        self.start_frame = Frame(master=window, relief=FLAT, borderwidth=1)
        self.start_frame.grid(row=0, column=0, padx=1, pady=1)
        self.start_button = Button(self.start_frame, text="start", fg="blue", command = lambda: self.los(True))
        self.start_button.pack()

        self.stop_frame = Frame(master=window, relief=FLAT, borderwidth=1)
        self.stop_frame.grid(row=0, column=2, padx=1, pady=1)
        self.stop_button = Button(self.stop_frame, text="stop", fg="red", command = lambda: self.los(False))
        self.stop_button.pack()

        self.Btn2_frame = Frame(master=window, relief=FLAT, borderwidth=1)
        self.Btn2_frame.grid(row=0, column=4, padx=1, pady=1)
        self.Btn2 = Button(self.Btn2_frame, text = "Beenden", command = root.destroy)
        self.Btn2.pack()

        self.Btn3_frame = Frame(master=window, relief=FLAT, borderwidth=1)
        self.Btn3_frame.grid(row=0, column=5, padx=1, pady=1)
        self.Btn3 = Button(self.Btn3_frame, text = "gerade", command = lambda: self.antworten_g())
        self.Btn3.pack()

        self.Btn4_frame = Frame(master=window, relief=FLAT, borderwidth=1)
        self.Btn4_frame.grid(row=0, column=6, padx=1, pady=1)
        self.Btn4 = Button(self.Btn4_frame, text = "ungerade", command = lambda: self.antworten_u())
        self.Btn4.pack()

        self.Btn5_frame = Frame(master=window, relief=FLAT, borderwidth=1)
        self.Btn5_frame.grid(row=0, column=7, padx=1, pady=1)
        self.Btn5 = Button(self.Btn5_frame, text = "null", command = lambda: self.antworten_n())
        self.Btn5.pack()
        

    def los(self, switch):
        global should_run
        should_run = switch


    def antworten_g(self):
        print("g")

    def antworten_u(self):
        print("u")

    def antworten_n(self):
        print("n")

root = Tk()
root.geometry("400x400")
app = gui(root)
root.mainloop()
Sirius3
User
Beiträge: 18216
Registriert: Sonntag 21. Oktober 2012, 17:20

*-Importe benutzt man nicht, weil damit unkontrolliert etliche Namen in den eigenen Namensraum geladen werden und die Übersichtlichkeit verloren geht.
Namen sollten aussagekräftig sein, einbuchstabige Namen sind das nicht, vor allem nicht für eine Klasse. Klassen werden per Konvention mit großem Anfangsbuchstaben geschrieben, Funktionen und Variablen dagegen komplett klein.
Was ist der Sinn, die Variablen zahl_u, zahl_n und zahl_g in a, b, c umzubenennen?

__init__ ist dazu da, eine Klasse zu initialisieren, nicht dass sie ewig läuft. Die Klasse `a` ist eh keine Klasse, weil sie keinerlei Zustand hat. Man könnte das ganze als Funktion schreiben.
Du solltest Dich entscheiden, ob Du ein Konsolenprogramm oder eines mit GUI schreibst, beides gleichzeitig sollte man nicht.
Eingaben sollten immer nur im Hauptthread gemacht werden, das gilt sowohl für GUIs als auch für Konsolenprogramme. Im Hintergrund sollten nur langlaufende Berechnungen oder andere Aktionen ohne Nutzerinteraktion stattfinden.
`global` darf in einem sauberen Programm nicht vorkommen. Um Zustand zwischen Methoden auszutauschen, hast Du ja schon eine Klasse geschrieben, so dass globale Variablen gar nicht nötig sind.
Das Hauptprogramm gehört in eine Funktion.

Code: Alles auswählen

import tkinter as tk
import random


class Gui:
    def __init__(self, window):
        self.number = tk.IntVar(window)
        self.result = tk.StringVar(window)

        button_frame = tk.Frame(window)
        button_frame.pack()
        tk.Button(button_frame, text="start", fg="blue", command=self.los).grid(
            row=0, column=0, padx=1, pady=1
        )
        tk.Button(button_frame, text="stop", fg="red", command=self.stop).grid(
            row=0, column=2, padx=1, pady=1
        )
        tk.Button(button_frame, text="Beenden", command=window.destroy).grid(
            row=0, column=4, padx=1, pady=1
        )
        tk.Button(button_frame, text="gerade", command=self.antworten_gerade).grid(
            row=0, column=5, padx=1, pady=1
        )
        tk.Button(button_frame, text="ungerade", command=self.antworten_ungerade).grid(
            row=0, column=6, padx=1, pady=1
        )
        tk.Button(button_frame, text="null", command=self.antworten_null).grid(
            row=0, column=7, padx=1, pady=1
        )
        label_frame = tk.Frame(window)
        label_frame.pack()
        tk.Label(label_frame, textvariable=self.number).pack()
        tk.Label(label_frame, textvariable=self.result).pack()

    def los(self):
        self.result.set("")
        self.number.set(random.randint(0, 10))

    def stop(self):
        self.result.set("stopped")

    def antworten_gerade(self):
        self.result.set("richtig" if self.number.get() % 2 == 0 else "falsch")

    def antworten_ungerade(self):
        self.result.set("richtig" if self.number.get() % 2 != 0 else "falsch")

    def antworten_null(self):
        self.result.set("richtig" if self.number.get() == 0 else "falsch")


def main():
    root = tk.Tk()
    app = Gui(root)
    root.mainloop()


if __name__ == "__main__":
    main()
Wenn Du jetzt noch eine Zeitkomponente einbauen möchtest, kennt Tkinter die after-Methode, die eine Funktion nach Ablauf einer bestimmten Zeit aufruft.
legion333
User
Beiträge: 7
Registriert: Montag 9. September 2024, 10:26
Wohnort: Berliner Umland

Guten Morgen Sirius3, ich danke dir für die fachlichen Anregungen. Ich habe diese zu ca. 80% verstanden! Deine Kompetenzen sind großartig! Habe sehr viel von dir in letzter Zeit gelernt. Danke dafür. Ich hätte auch nie damit gerechnet, dass du mir in so einer kurzen Zeitspanne einen Quellcode präsentierst! Top Leistung!

Also zu deinen Fragen:

ich wollte quasi eine GUI scheiben die eine Schleife mit ausgewählten anzahl von schritten (im Quellcode zunächst 10 Schritte) immer wieder aus einer Tupelliste per Zufall, Zahlen (vieleicht später auch Wörter für gramatik Übungen) ausgibt und diese mit anderen Tuppellisten vergleicht. Der Nutzer Kriegt quasi eine Frage die er mit Knöpfen und nicht mit eingeben von Buchstaben beantwortet.

Unten steht der Anfangscode wie ich es mir vorgestellt habe, nur ist dieser eben Konsolenbasiert und nicht GUI.

Code: Alles auswählen

from random import choice

zahl = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
zahl_u = ['1', '3', '5', '7', '9', '11', '13', '15', '17', '19']

zahl_n = ['0']
zahl_g = ['2', '4', '6', '8', '10', '12', '14', '16', '18', '20']


for i in range(10):
    
    a = choice(zahl)
    b = zahl_u
    c = zahl_n
    d = zahl_g

    print(a)

    if a in b:
        antw = input('Ist die Zahl gerade? Dann drücke "g" oder Ungerade? Dann bitte "u" Drücken: ')
        if antw == 'u':
            print('Richtig!'+'\n')
        elif antw != 'u':
            print('Falsch!'+'\n')
        print('ungerade!'+'\n')

    elif a in c:
        antw = input('Ist die Zahl gerade? Dann drücke "g" oder Ungerade? Dann bitte "u" Drücken oder "n" für Null: ')
        if antw == 'n':
            print('Richtig!'+'\n')
        elif antw != 'n':
            print('Falsch!'+'\n')
        print('null!'+'\n')

    elif a in d:
        antw = input('Ist die Zahl gerade? Dann drücke "g" oder Ungerade? Dann bitte "u" Drücken: ')
        if antw == 'g':
            print('Richtig!'+'\n')
        elif antw != 'g':
            print('Falsch!'+'\n')
        print('gerade!'+'\n')
Sirius3
User
Beiträge: 18216
Registriert: Sonntag 21. Oktober 2012, 17:20

Es erhöht die Lesbarkeit, wenn man sprechende Variablennamen verwendet. Ein `if ausgewaehlte_zahl in ungerade_zahlen` ist deutlich aussagekräftiger als `if a in d`.
Variablennamen, die Listen repräsentieren benennt man im Plural. Warum Du aus zahl_u ein b machst, hast Du mir noch nicht erklärt.
Dass nur wenn die Zahl 0 ist auch die Frage nach der 0 kommt, ist etwas komisch. Eigentlich hast Du ja bei allen möglichen Zahlen immer die selbe Frage, die deshalb auch vor die if-Kaskade gestellt werden kann.
Statt drei mal die selbe richtig-falsch-Ausgabe speichert man, ob die Antwort richtig war, am besten in einer Variable.

Code: Alles auswählen

from random import choice

alle_zahlen = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
ungerade_zahlen = ['1', '3', '5', '7', '9', '11', '13', '15', '17', '19']
null_zahlen = ['0']
gerade_zahlen = ['2', '4', '6', '8', '10', '12', '14', '16', '18', '20']


for i in range(10):
    ausgewaeehlte_zahl = choice(alle_zahlen)
    print(ausgewaeehlte_zahl)
    antwort = input('Ist die Zahl gerade? Dann drücke "g" oder Ungerade? Dann bitte "u" Drücken oder "n" für Null: ')
    if ausgewaeehlte_zahl in gerade_zahlen:
        richtig = antwort == "g"
        loesung = "gerade"
    elif ausgewaeehlte_zahl in ungerade_zahlen
        richtig = antwort == "u"
        loesung = "ungerade"
    elif ausgewaeehlte_zahl in null_zahlen:
        richtig = antwort == "n"
        loesung = "null"
    if richtig:
        print("Richtig!")
    else:
        print(f"Falsch! Die Zahl war {loesung}.")
    print()
Bei GUIs muß man aber komplett umdenken. Da gibt es keinen linearen Programmablauf mehr, sondern man kann nur auf Ereignisse reagieren. Jedes Ereignis ändert dann den Zustand des Programms und der Zustand wird entsprechend im Fenster dargestellt.
Beim Drücken auf "Start" wird z.B. der Aufgabenzähler um 1 erhöht, und beim Drücken auf Antworten wird geprüft, ob der Aufgabenzähler auf 10 steht und dann mit einer Meldung "Juhu, alle Aufgaben gelöst" das Programm beendet.
legion333
User
Beiträge: 7
Registriert: Montag 9. September 2024, 10:26
Wohnort: Berliner Umland

Hallo Sirius3 ich danke dir nochmals!

ich wollte zu ja quasi zu Übung 3 py-dateien haben und in einer die Werte zunächst aufrufen desswegen habe ich im GUI versuch dies geschrieben:

a = choice(Zahlen.zahl)
b = Uebung1.zahl_u
c = Uebung2.zahl_n
d = Uebung2.zahl_g

habe dies aber, um leichter später zu Arbeiten (kopie/paste), schon bei dem ersten angewandt...

für mich ist "if a in b:" leichter als "if ausgewaehlte_Zahl in ungerade_Zahl:" so vermeide ich schreibfehler... ich arbeite auch ohne PyCharm oder ähnliches.

Ich werde mir jetzt angewöhnen dies zu vermeiden und ordentliche Namen zu verteilen!
legion333
User
Beiträge: 7
Registriert: Montag 9. September 2024, 10:26
Wohnort: Berliner Umland

Meinst du jetzt mit "Bei GUIs muß man aber komplett umdenken. Da gibt es keinen linearen Programmablauf mehr, sondern man kann nur auf Ereignisse reagieren." dass ich jetzt die Idee "mit Knöpfen auf eine unendliche Schleife antworten" eher vergessen kann? Es wäre doch was wenn man ein Program Startet, auf Start drückt und bekommt eine Frage, die mein Kind per Buttons beantworten kann, während einer vorgegebenen Zeit. Man könnte auch am Ende Anzeigen "Du hast 10 Fehler von 100" oder du warst "3 mal nicht schnell genug" das wäre doch für kinder eher Spannender als Wörter oder Buchstaben auf ner Konsole zu tippen... Klar könnte man jetzt auch was gutes mit PyGame zaubern aber für mich ist das ja noch zukunftsmusik, siehst du ja selber an meinem Code... ^^
Sirius3
User
Beiträge: 18216
Registriert: Sonntag 21. Oktober 2012, 17:20

Ich habe nicht geschrieben, dass es nicht geht, sondern dass Du umdenken mußt. Schleifen gehen halt nicht.
legion333
User
Beiträge: 7
Registriert: Montag 9. September 2024, 10:26
Wohnort: Berliner Umland

Werter Sirius3, magst du mir da eventuell einen kleinen Denkanstoß geben?

Und könnte man denn mit Schleifen in PyGames ähnliche Ideen lösen?
Benutzeravatar
Dennis89
User
Beiträge: 1503
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

schau dir mal `after()` an, das tkinter mitbringt.


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
legion333
User
Beiträge: 7
Registriert: Montag 9. September 2024, 10:26
Wohnort: Berliner Umland

Danke für den Tipp Dennis89!
legion333
User
Beiträge: 7
Registriert: Montag 9. September 2024, 10:26
Wohnort: Berliner Umland

Ich danke Euch beiden!!! Und die Lösung ist echt simple...

Code: Alles auswählen

import tkinter as tk
import random

window = tk.Tk()
window.geometry('400x200')

number = tk.IntVar(window)
result = tk.StringVar(window)

def los():
    result.set("")
    number.set(random.randint(0, 10))

def stop():
    result.set("stopped")

def antworten_gerade():
    result.set("richtig" if number.get() % 2 == 0 else "falsch")
    window.after(2000, los)

def antworten_ungerade():
    result.set("richtig" if number.get() % 2 != 0 else "falsch")
    window.after(2000, los)

def antworten_null():
    result.set("richtig" if number.get() == 0 else "falsch")
    window.after(2000, los)

button_frame = tk.Frame(window)
button_frame.pack()

tk.Button(button_frame, text="start", fg="blue", command=los).grid(row=0, column=0, padx=1, pady=1)

tk.Button(button_frame, text="stop", fg="red", command=stop).grid(row=0, column=2, padx=1, pady=1)
        
tk.Button(button_frame, text="Beenden", command=window.destroy).grid(row=0, column=4, padx=1, pady=1)
        
tk.Button(button_frame, text="gerade", command=antworten_gerade).grid(row=0, column=5, padx=1, pady=1)
        
tk.Button(button_frame, text="ungerade", command=antworten_ungerade).grid(row=0, column=6, padx=1, pady=1)
        
tk.Button(button_frame, text="null", command=antworten_null).grid(row=0, column=7, padx=1, pady=1)

       
label_frame = tk.Frame(window)
label_frame.pack()

tk.Label(label_frame, textvariable=number).pack()
tk.Label(label_frame, textvariable=result).pack()


window.mainloop()


Eine Frage noch...

Warum hat das Objekt Class Gui keine Methode after()?
Sirius3
User
Beiträge: 18216
Registriert: Sonntag 21. Oktober 2012, 17:20

Dass Du eine Klasse brauchst, hatte ich Dir ja schon geschrieben und das Beispiel dazu auch.
Ein sauberes Programm darf keine globalen Variablen haben.
Dass meine GUI-Klasse kein after kennt liegt daran, dass es nicht von einem tk-Widget erbt. Aber Du kannst Dir ja einfach window in self speichern und das dann benutzen.
Antworten