Button Command

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Bulli03
User
Beiträge: 11
Registriert: Donnerstag 17. Oktober 2019, 12:47

Hallo, Ich bin grade dabei ein Kopfrechentrainer zu programmieren mit Tkinter. Das Problem dahinter ist allerdings das ich nicht weiß wie ich einen Button sagen soll, er möge ein Textfeld überprüfen und eine jeweilige vorprogrammierte Aktion durchzuführen. So wie bei if / else. Leider finde ich dazu nichts im Internet.
Ich hoffe man versteht was ich meine.

Mit freundlichen Grüßen
Bulli

Code: Alles auswählen

from tkinter import *
from random import *

class MyButton(Button):
    def Kopfrechner (self):
        einfügen1 = int(ei1.get())
        einfügen1.delete(0,'end')

fenster = Tk()

z1= randint(1, 100)
z2= randint(1, 100)

fenster.geometry("350x350")
fenster.title("Python-Kopfrechentrainer")
rahmen = Frame(fenster, relief="ridge", borderwidth=5)
rahmen.pack(fill="both", expand = 1)

label1 = Label(rahmen, text = "Zahl 1:")
label1.place(x = 50, y = 30)
label2 = Label(rahmen, text = "+")
label2.place(x = 150, y = 65)
label3 = Label(rahmen, text = "Zahl 2:")
label3.place(x = 250, y = 30)
label4 = Label(rahmen, text = z1)
label4.place(x = 60, y = 75)
label5 = Label(rahmen, text = z2)
label5.place(x = 260, y = 75)

label6 = Label(rahmen, text = "Ergebnis:")
label6.place(x = 70, y = 200)
label7 = Label(rahmen, text = "Vorherige Lösung: ")
label7.place(x = 70, y = 220)

button1 = Button(rahmen, text = "Exit", command = fenster.destroy)
button1.place(x = 25, y = 290)

einfügen1 = Entry(rahmen, bd = 2, width = 10)
einfügen1.place (x = 175 , y = 200)

button2 = Button(rahmen, text = "Weiter") #Command für eine neue Aufgabe.
button2.place(x = 275, y = 290)

ausgabe1 = Label(rahmen, text= z1 + z2) #Zeigt die Lösung der vorherigen Aufgabe.
ausgabe1.place (x = 200, y = 220)


fenster.mainloop()
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

@Bulli03: benutze keine *-Importe, weil man sich damit unkontrollierbar hunderte Namen in den eigenen Namensraum schaufelt. Tkinter wird üblicherweise via `import tkinter as tk` importiert und alle Namen per tk.xyz angesprochen.

Die Klasse `MyButton` wird gar nicht benutzt, ist auch gar nicht nötig, und die Funktion Kopfrechnen sollte alles was sie braucht über ihre Argumente bekommen. ei1 ist nirgens definiert, was soll das sein?
Nicht jedes Label muß man an einen Namen binden, wenn man es später nicht mehr braucht. place sollte man nicht verwenden, sondern statt dessen mit grid oder pack arbeiten.

So könnte das aussehen (ich habe kopfrechnen mal in pruefe_ergebnis umbenannt, aber bei Dir fehlt noch der Code, wo denn das aufgerufen werden soll und wie die Prüfung tatsächlich passieren soll. Dazu fehlen auch noch die Argumente zahl1 und zahl2):

Code: Alles auswählen

import tkinter as tk
from random import randint

def pruefe_ergebnis(einfügen):
    ergebnis = int(einfügen.get())
    einfügen.delete(0,'end')

def main():
    zahl1 = randint(1, 100)
    zahl2 = randint(1, 100)

    fenster = tk.Tk()
    fenster.geometry("350x350")
    fenster.title("Python-Kopfrechentrainer")
    rahmen = tk.Frame(fenster, relief="ridge", borderwidth=5)
    rahmen.pack(fill="both", expand = 1)

    tk.Label(rahmen, text="Zahl 1:").place(x=50, y=30)
    tk.Label(rahmen, text="+").place(x=150, y=65)
    tk.Label(rahmen, text="Zahl 2:").place(x=250, y=30)
    label_zahl1 = tk.Label(rahmen, text=zahl1)
    label_zahl1.place(x=60, y=75)
    label_zahl2 = tk.Label(rahmen, text=zahl2)
    label_zahl2.place(x=260, y=75)

    tk.Label(rahmen, text="Ergebnis:").place(x=70, y=200)
    tk.Label(rahmen, text="Vorherige Lösung: ").place(x=70, y=220)
    tk.Button(rahmen, text="Exit", command=fenster.destroy).place(x=25, y=290)

    einfügen = tk.Entry(rahmen, bd=2, width=10)
    einfügen.place(x=175, y=200)

    tk.Button(rahmen, text="Weiter").place(x=275, y=290)
    ausgabe1 = tk.Label(rahmen, text=zahl1 + zahl2) #Zeigt die Lösung der vorherigen Aufgabe.
    ausgabe1.place(x=200, y=220)
    fenster.mainloop()
    
if __name__ == '__main__':
    main()
Zum Binden von pruefe_ergebnis an einen Button braucht man functools.partial. Die Benutzung dazu wird in etlichen Beiträgen hier im Forum erklärt.
Benutzeravatar
__blackjack__
User
Beiträge: 13073
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Bulli03: Es sieht so aus als solltest Du erst einmal die Grundlagen lernen, inklusive objektorientierter Programmierung (OOP), also eigene Klassen schreiben. Denn das braucht man für jede nicht-triviale GUI.

Die Klasse die Du da hast macht keinen Sinn. Du willst keinen eigenen `Button` ableiten, denn an dessen Eigenschaften musst Du ja gar nichts ändern. Tust Du ja auch nicht wirklich, denn die eine vorhandene Methode funktioniert so überhaupt gar nicht. `ei1` gibt es nicht. Und wenn Du `einfügen1` eine ganze Zahl zuweist, dann hat die ganz sicher keine `delete()`-Methode.

Sonstige Anmerkungen: Sternchen-Importe sind Böse™. Damit holt man sich gerade bei `tkinter` über 100 Namen ins Modul von denen nur ein kleiner Bruchteil wirklich gebraucht wird. Und nicht nur welche die im `tkinter`-Modul definiert werden, sondern aucn alles was das `tkinter`-Modul selber von woanders importiert hat.

Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Funktionen und Methoden sollten alles was sie ausser Konstanten benötigen als Argument(e) übergeben bekommen. `MyButton.Kopfrechner()` dürfte also weder `ei1` noch `einfügen1` einfach so verwenden.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). `Kopfrechner` wäre also von der Schreibweise ein Name für eine Klasse, keiner für eine Methode.

Funktionen und Methoden werden üblicherweise nach der Tätigkeit benannt die sie durchführen, damit der Leser weiss was die tun. `Kopfrechner` ist aber keine Tätigkeit. Bei Rückruffunktionen gibt es auch die Konvention das man sie `on_event_name()` nennen kann.

`My` ist so gut wie *nie* ein sinnvoller Präfix, weil der nichts aussagt. Wenn man einen spezialisierteren `Button` programmiert sollte dessen Klassenname dem Leser vermitteln was ihn inhaltlich vom normalen `Button` unterscheidet.

`einfügen` wäre dagegen ein Name hinter dem man als Leser eine Funktion/Methode erwarten würde und kein Eingabefeld.

Man nummeriert keine Namen durch. Dann will man sich entweder bessere Namen ausdenken, oder gar keine Einzelnamen sondern eine Datenstruktur verwenden. Oft eine Liste.

Einzig die beiden Operanden könnte man mit einer Nummer versehen, in solchen Fällen verwende ich dann aber lieber `_a` und `_b` als Suffix, oder schreibe es aus: `first_operand`, `second_operand`.

So einige Namen in dem Programm braucht man auch gar nicht wirklich, weil der Wert danach nie wieder benötigt wird.

Man verwendet kein `place()`. Das funktioniert nicht systemübergreifend weil es viele verschiedene Bildschirmgrössen, -auflösungen, und -einstellungen gibt. Bei mir stösst die „Weiter“-Schaltfläche beispielsweise fast an den rechten Fensterrahmen.

Das "+" ist nicht in der Mitte zwischen den beiden Zahlen, weder horizontal noch vertikal, und der „Vorherige Lösung“-Text ist viel zu dicht an der Zeile darüber: Die Zahl überlappt den Rahmen vom Eingabefeld:
Bild

Du verwendest an einigen Stellen literale Zeichenketten für die `tkinter` Konstanten definiert hat. Das vermindert das Risiko das man sich vertippt, und der Leser weiss, dass es da anscheinend feste Werte gibt und nicht jede beliebige Zeichenkette Sinn macht.

Der vorhandene Code überarbeitet:

Code: Alles auswählen

#!/usr/bin/env python3
from random import randint
import tkinter as tk


def main():
    first_operand = randint(1, 100)
    second_operand = randint(1, 100)

    fenster = tk.Tk()
    fenster.title("Python-Kopfrechentrainer")

    main_frame = tk.Frame(fenster, relief=tk.RIDGE, borderwidth=5)
    main_frame.pack(expand=True, fill=tk.BOTH)

    frame = tk.Frame(main_frame)
    tk.Label(frame, text="Zahl 1:").grid(row=0, column=0)
    tk.Label(frame, text="Zahl 2:").grid(row=0, column=2)

    first_operand_label = tk.Label(frame, text=first_operand)
    first_operand_label.grid(row=1, column=0)

    tk.Label(frame, text="+").grid(row=1, column=1)
    second_operand_label = tk.Label(frame, text=second_operand)
    second_operand_label.grid(row=1, column=2)
    frame.pack()

    frame = tk.Frame(main_frame)
    tk.Label(frame, text="Ergebnis:").pack(side=tk.LEFT)
    result_entry = tk.Entry(frame, bd=2, width=10)
    result_entry.pack(side=tk.LEFT)
    frame.pack()

    frame = tk.Frame(main_frame)
    tk.Label(frame, text="Vorherige Lösung: ").pack(side=tk.LEFT)
    output_label = tk.Label(frame, text=first_operand + second_operand)
    output_label.pack(side=tk.LEFT)
    frame.pack()

    frame = tk.Frame(main_frame)
    tk.Button(frame, text="Exit", command=fenster.destroy).pack(side=tk.LEFT)
    tk.Button(frame, text="Weiter").pack(side=tk.RIGHT)
    frame.pack(expand=True, fill=tk.X)

    fenster.mainloop()


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Bulli03
User
Beiträge: 11
Registriert: Donnerstag 17. Oktober 2019, 12:47

Danke erstmal für die hilfreichen Informationen und Hilfestellungen.
Aber ich habe ja dennoch das Problem das wenn man auf den Button "Weiter" drückt, der Button nicht das Textfeld bei Ergebnisse abfragt und wenn die Zahl da entsprechend richtig ist bzw. falsch er eine Aktion ausführt. Wie zum beispiel, dass Programm gibt die Zahlen 20 und 58 vor. Beim Ergebnis wird 78 eingetragen. Das ist richtig und durch das drücken von dem Knopf "Weiter" wird dann eine Aktion durchgeführt.

Mit freundlichen Grüßen
Bulli
Benutzeravatar
__blackjack__
User
Beiträge: 13073
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Bulli03: Das Problem kann man durch `functools.partial()` und eine Indirektion per `tkinter.IntVar`-Objekten für die beiden Summanden lösen, oder eben durch objektorientierte Programmierung in dem man eine entsprechende Klasse für die GUI schreibt.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Bulli03
User
Beiträge: 11
Registriert: Donnerstag 17. Oktober 2019, 12:47

@__blackjack__: Und wie mache ich das genau?
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Wie ich schon geschrieben hatte, wird dieses Thema in Gefühlt 1007 Beiträgen hier behandelt:
search.php?keywords=partial
Bulli03
User
Beiträge: 11
Registriert: Donnerstag 17. Oktober 2019, 12:47

Aber wie wende ich das an?
Benutzeravatar
noisefloor
User
Beiträge: 3854
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

der bessere Weg ist nach wie vor der von __blackjack__ bereits genannte Weg über eine Klasse für die GUI. Alles andere macht auch hier wenig Sinn.

Wenn du eine Klasse hast, kannst du dir alles, was du benötigst, über die Attribute der Klasse "merken".

In der offiziellen Python-Doku ist das Grundgerüst für eine Klasse für eine tkinter GUI aufgeführt.

Gruß, noisefloor
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

@Bulli03: wo kommst Du denn konkret nicht weiter?
Bulli03
User
Beiträge: 11
Registriert: Donnerstag 17. Oktober 2019, 12:47

@Sirius3: Ich komme mit dem functools.partial() nicht weiter, ich habe mir viele Sachen durchgelesen, aber ich versteh nicht wie ich das Anwenden kann.
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

@Bulli03: wenn man etwas nicht versteht, dann macht man sich das am Besten an Beispielen klar.

Code: Alles auswählen

from functools import partial
def test(a,b,c):
    print(a,b,c)

test_a = partial(test, 17)
print(test_a(1, 2))
test_b = partial(test, b=17)
print(test_a(1, 2))
test_c = partial(test, 42, 17)
print(test_c(1))
Bulli03
User
Beiträge: 11
Registriert: Donnerstag 17. Oktober 2019, 12:47

Sirius3: Erstmal danke für die Hilfe, ich habe das jetzt so eingesetzt wo ich es für scheinbar richtig halte aber es fehlt ihm immer noch ein Argument. Ich weiß aber nicht wo er es haben will und vor allem was er genau haben möchte.

Code: Alles auswählen

import tkinter as tk
from random import randint
from functools import partial

def pruefe_ergebnis(einfügen):

    ergebnis = int(einfügen.get())
    einfügen.delete(0,'end')
    print(ergebnis_a)
    zahl1 = randint(1, 100)
    zahl2 = randint(1, 100)
    preufe_ergebnis_a = partial(ergebnis == zahl1+zahl2)

def main():

    zahl1 = randint(1, 100)
    zahl2 = randint(1, 100)
    
    fenster = tk.Tk()
    fenster.geometry("350x350")
    fenster.title("Python-Kopfrechentrainer")
    rahmen = tk.Frame(fenster, relief="ridge", borderwidth=5)
    rahmen.pack(fill="both", expand = 1)

    tk.Label(rahmen, text="Zahl 1:").place(x=50, y=30)
    tk.Label(rahmen, text="+").place(x=150, y=65)
    tk.Label(rahmen, text="Zahl 2:").place(x=250, y=30)
    label_zahl1 = tk.Label(rahmen, text=zahl1)
    label_zahl1.place(x=60, y=75)
    label_zahl2 = tk.Label(rahmen, text=zahl2)
    label_zahl2.place(x=260, y=75)

    tk.Label(rahmen, text="Ergebnis:").place(x=70, y=200)
    tk.Button(rahmen, text="Exit", command=fenster.destroy).place(x=25, y=290)

    einfügen = tk.Entry(rahmen, bd=2, width=10)
    einfügen.place(x=175, y=200)

    tk.Button(rahmen, text="Weiter", command=pruefe_ergebnis).place(x=275, y=290)
    fenster.mainloop()

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

command erwartet eine Funktion ohne Parameter, pruefe_ergebnis braucht aber eins. Daher musst Du partial dort einsetzten. Der Inhalt von pruefe_ergebnis ist komplett geraten und macht keinen Sinn. Beschreib mal, was da gemacht werden soll.
Bulli03
User
Beiträge: 11
Registriert: Donnerstag 17. Oktober 2019, 12:47

Das Programm gibt zwei Zahlen vor, zahl1 und zahl2. Diese soll der User im Kopf zusammen rechnen und wenn er das getan hat schreibt er das Ergebnis in das Textfeld neben "Ergebnis:". Dann soll der User auf den Button "Weiter" drücken. Wenn er das getan hat, soll der Button bzw. das Programm das Textfeld abfragen, zwei neue zahlen generieren und eventuell soll dann noch eine Aktion ausgeführt werden, wie zum Beispiel wenn der User den Knopf gedrückt hat soll unter dem Feld "Ergebnis" und dessen Textfeld, das Ergebnis der vorherigen runde stehen. Wie zum Beispiel: "Das Ergebnis war: Ergebnis_Vorherige_Runde" oder wenn das Ergebnis falsch war: "Das richtige Ergebnis der vorhereigne Runde war: Richtiges_Ergebnis_Vorherige_Runde" aber das denn nur wenn das nicht zu kompliziert ist bzw. überhaupt möglich ist.
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Also brauchst Du in Deiner pruefe-Funktion Zugriff auf die Labels für die Zahlen und das Ergebnislabel. Die mußt Du alle per Parameter übergeben.
Bulli03
User
Beiträge: 11
Registriert: Donnerstag 17. Oktober 2019, 12:47

Und wie mache ich das? Auch mit partial?
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Antwort kennst Du bereits.
Bulli03
User
Beiträge: 11
Registriert: Donnerstag 17. Oktober 2019, 12:47

Ich glaube nicht, sonst wüsste ich ja was ich zu tun habe. Oder würde verstehen was ich bitte zutun habe.
__deets__
User
Beiträge: 14523
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ja, auch mit Partial. Partial dient dazu, Argumente an eine Funktion zu binden.
Antworten