Butten auf Userfrom suchen über Beschriftung

Fragen zu Tkinter.
Antworten
IC-Cruncher
User
Beiträge: 4
Registriert: Dienstag 2. Mai 2023, 19:02

Hallo Zusammen,

ich habe ein kleines Problem mit zwei Buttons auf einer Userform.
Unten habe ich ein Beispielprogramm angehängt.

ich habe eine Userform mit zwei Buttons generiert.
Die Buttons werden absichtlich mit dem gleichen Namen "taste" generiert und palziert.
in dem richtigen Programm, generiere ich ein variabels Tastenfeld über eine Liste mit Hilfe einer For-Schleife.

Jetzt soll beim drücken des einzelenen Buttons, die Hintergrundfarbe geändert werden.

ist es Möglich einen Button anhand der Beschriftung, in diesem Beispiel "+" und "-", auf der Userform zu suchen und die Hintergrundfarbe zu ändern?
oder gibt es eine andere Möglichkeit?

Grüße aus dem Sauerland

Code: Alles auswählen

# --------------------------------------------------------------------------------
# testprogramm
# --------------------------------------------------------------------------------
# Bibliotheken
import tkinter as tk

# --------------------------------------------------------------------------------
# Initialisierungen
# Userform
root = tk.Tk()
root.title("Testprogramm")
root.geometry("225x100")

# --------------------------------------------------------------------------------
# Funktionen
def tastenauswertung():
    print("Tastenauswertung")
    # wenn Taster "+" gedrückt, dann Taster "+" blau
    # wenn Taster "-" gedrückt, dann Taster "-" rot

# --------------------------------------------------------------------------------
# Tasten initialisieren
taste = tk.Button(root)
taste["text"] = "+"
taste["command"] = tastenauswertung # taste1, taste1["text"])
taste["font"] = 20
taste.place(x=25, y=25, width=75, height=50)

taste = tk.Button(root)
taste["text"] = "-"
taste["command"] = tastenauswertung
taste["font"] = 30
taste.place(x=125, y=25, width=75, height=50)

# Hauptschleife
root.mainloop()
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,
IC-Cruncher hat geschrieben: Freitag 23. Februar 2024, 18:42 Die Buttons werden absichtlich mit dem gleichen Namen "taste" generiert und palziert.
Wieso?

Da kann man sich was zusammen basteln, du musst halt irgendwas an `tastenauswertung` übergeben, damit die Funktion weis, was sie zu tun hat.

Code: Alles auswählen

# --------------------------------------------------------------------------------
# testprogramm
# --------------------------------------------------------------------------------
# Bibliotheken
import tkinter as tk
from functools import partial

# --------------------------------------------------------------------------------
# Initialisierungen
# Userform
root = tk.Tk()
root.title("Testprogramm")
root.geometry("225x100")

# --------------------------------------------------------------------------------
# Funktionen
def tastenauswertung(action):
    if action == "+":
        print("blau")
    elif action == "-":
        print("rot")
    # wenn Taster "+" gedrückt, dann Taster "+" blau
    # wenn Taster "-" gedrückt, dann Taster "-" rot

# --------------------------------------------------------------------------------
# Tasten initialisieren
taste = tk.Button(root)
taste["text"] = "+"
taste["command"] = partial(tastenauswertung, "+") # taste1, taste1["text"])
taste["font"] = 20
taste.place(x=25, y=25, width=75, height=50)

taste = tk.Button(root)
taste["text"] = "-"
taste["command"] = partial(tastenauswertung, "-")
taste["font"] = 30
taste.place(x=125, y=25, width=75, height=50)

# Hauptschleife
root.mainloop()
Allgemeines: Auf Modulebene darf kein ausführbarer Code stehen, hier werden nur Klassen, Funktionen, Konstanten und der Einstiegspunkt in die `main`-Funktion definiert.
Alles was bei dir auf Modulebene steht gehört in die `main`-Funktion. Daraus wird üblicherweise das Programm gesteuert. Kommentare sollten einen Mehrwert bieten, bei dir verwirrt das eher und stört das lesen.
Du kannst beim erstellen des Buttons `text` und `command` gleich mit angeben. `place` sollte man nicht verwenden, weil dadurch das Programm auf anderen Bildschirmen anders aussehen kann oder gar unbenutzbar werden kann. Verwende besser `grid`. Verwende auch sprechende Namen, das erleichtert das Lesen, Verstehen und Warten des Codes.

Zwischenstand:

Code: Alles auswählen

import tkinter as tk
from functools import partial


def tastenauswertung(color):
    if color in ["blau", "rot"]:
        print(color)


def main():
    root = tk.Tk()
    root.title("Testprogramm")
    taste_blau = tk.Button(
        root, text="+", font=20, command=partial(tastenauswertung, "blau")
    )
    taste_blau.grid(row=0, column=0)
    taste_rot = tk.Button(
        root, text="-", font=20, command=partial(tastenauswertung, "rot")
    )
    taste_rot.grid(row=0, column=1)

    root.mainloop()


if __name__ == "__main__":
    main()
Ich würde vielleicht eher ein Dictonary anstatt einer Liste nehmen:

Code: Alles auswählen

import tkinter as tk
from functools import partial

BUTTONS = {"-": "rot", "+": "blau"}


def tastenauswertung(color):
    if color in ["blau", "rot"]:
        print(color)


def main():
    root = tk.Tk()
    root.title("Testprogramm")
    for index, button in enumerate(BUTTONS.items()):
        name, color = button
        taste = tk.Button(
            root, text=name, font=20, command=partial(tastenauswertung, color)
        )
        taste.grid(row=0, column=index)
    root.mainloop()


if __name__ == "__main__":
    main()
Grüße
Dennis

Edit: Die meisten GUI's erfordern das Programmieren mit Klassen. Nur recht einfach Programme kommen ohne aus. Du musst jetzt natürlich an die Funktion, die die Tasten auswertet auch noch ein 'tk'-Objekt übergeben, damit du den Hintergrund ändern kannst.
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Kommentare sollten dem Leser einen Mehrwert bieten, Kommentare die nur aus 81 Minuszeichen bestehen, sind das garantiert nicht. Auch Kommentare, die das offensichtliche (Funktionen) beschreiben, sind überflüssig.
Jeder vernünftige Editor erkennt die Struktur des Programms und bietet die entsprechenden visuellen Hinweise automatisch.
Manche Platformen erlauben die visuelle Änderung von Buttons nicht, insgesamt sollte man nicht von der im System eingestellten Darstellung abweichen, weil der Nutzer das vielleicht absichtlich so eingestellt hat. Also Größen, Farben usw nur bei äußerst wichtigen Gründen ändern.
Wenn man auf das Tastenobjekt später wieder zugreifen will, dann übergibt man dieses eben an die Funktion.
Das dazu passende Programm hat ja Dennis bereits gepostet.
IC-Cruncher
User
Beiträge: 4
Registriert: Dienstag 2. Mai 2023, 19:02

Hallo Dennis,

danke für deine ausführliche Antwort.
Zunächst ein kurzer Zwischenstand.

ich muss , glaube ich, etwas ausholen.
Das mit der main-Funktion kannte ich nocht nicht.
ich bin parallel den Lehrgang "python lernen" noch nicht so weit. :D

ich habe mein Programm aber entsprechend angepasst.
Die Kommentar habe ich entfernt :D
ich habe das richtige Programm mal angehängt.
Zum Programm:
Es soll ein Taschenrechner werden.
Das Tastenfeld soll variable sein. Deswegen eine 2D-Liste.
Die Tasten sollen verschiedene Größen haben. (gleiche Namen nebeneinander)
Die Eingabe soll per Tastatur oder Tastenfeld auf der Userform erfolgen.

Wenn ein Operation eingegeben wird, soll die taste eingefärbt werden.
Deswegen hatte ich die Frage einen Button per Beschriftung anzusprechen.

Bei meinem Programm bin ich nun soweit, dass das Tastenfeld aufgebaut wird.
Es sind auch nur Eingaben per Tatatur möglich, die in der 2D-Liste aufegführt sind.

ich hoffe das Programm ist nicht allzu schlecht programiert. :mrgreen:

ich werde morgen weiter machen.

Code: Alles auswählen

# --------------------------------------------------------------------------------
# Taschenrechner V1.0
import tkinter as tk
from functools import partial

beschriftung = {"Escape": "ESC",
                "slash": "/",
                "asterisk": "*",
                "minus": "-",
                "plus": "+",
                "Return": "=",
                "comma": ","}
                
# zwei Tastenfelder zur Auswahl
tastenfeld1 = [["Escape","/","*","-"],
              ["7","8","9","+"],
              ["4","5","6","+"],
              ["1","2","3","="],
              ["0","0",",","="]]
tastenfeld2 = [["ESC","ESC","*","-"],
              ["ESC","ESC","9","+"],
              ["","5","6","+"],
              ["1","2","3","="],
              ["0","0",",","="]]

tastenfeld = tastenfeld2

rand = 10
abstand = 5
groesse = 50
schriftgroesse = groesse /2

anzahl_x = len(tastenfeld[0])
anzahl_y = len(tastenfeld)

def tastenauswertung(name, taste):
    print("Eingabe", name)

def key_handler(event):  
    try: #test = tastenfeld[0].index(event.keysym)
        eingabe = beschriftung[event.keysym]
    except:
        eingabe = event.keysym
    liste = []
    for i in range(anzahl_y):
        for j in range(anzahl_x):
            liste.append(tastenfeld[i][j])
    try:
        index = liste.index(eingabe)
        tastenauswertung(eingabe, "")
    except:
        index = ""
        
def main():
    # Userform erstellen
    root = tk.Tk()
    root.title("Testprogramm")
    uform_x = (2*rand)+(anzahl_x*groesse)+((anzahl_x-1)*abstand)
    uform_y = (3*rand)+groesse+(anzahl_y*groesse)+((anzahl_y-1)*abstand)
    root.minsize(uform_x, uform_y)
    root.maxsize(uform_x, uform_y)
    root.geometry(str(uform_x) + "x" + str(uform_y))

    # Eingabefeld erstellen
    breite = (anzahl_x*groesse)+((anzahl_x-1)*abstand)
    hoehe = groesse

    eingabefeld = tk.Entry(root)
    eingabefeld_wert = tk.StringVar()
    eingabefeld["textvariable"] = eingabefeld_wert
    eingabefeld["justify"] = tk.RIGHT
    eingabefeld["font"] = schriftgroesse
    eingabefeld["state"] = "disabled"
    eingabefeld_wert.set("0")
    eingabefeld.place(x=rand, y=rand, width=breite, height=hoehe)

    # tastenfeld erstellen
    tastenverarbeitung = [[0 for x in range(anzahl_x)]for y in range(anzahl_y)]
    for lv_y in range(anzahl_y):
        for lv_x in range(anzahl_x):
            if tastenverarbeitung[lv_y][lv_x] == 0:
                #print(tastenverarbeitung)
                name = tastenfeld[lv_y][lv_x]
                if name !="":
                    groesse_x = 0
                    dx = 0
                    for dx in range(anzahl_x):
                        vergleich = tastenfeld[lv_y][dx]
                        if name == vergleich:
                            groesse_x +=1
                        else:
                            continue
                    groesse_y = 0
                    dy = 0
                    for dy in range(anzahl_y):
                        vergleich = tastenfeld[dy][lv_x]
                        if name == vergleich:
                            groesse_y +=1
                        else:
                            continue
                    
                    for i in range(groesse_y):
                        for j in range(groesse_x):
                            tastenverarbeitung[lv_y+i][lv_x+j] = 1
                
                    x_pos = rand+(lv_x*groesse)+(lv_x*abstand)
                    y_pos = (2*rand+groesse)+(lv_y*groesse)+(lv_y*abstand)
                    breite = (groesse_x*groesse)+((groesse_x-1)*abstand)
                    hoehe = (groesse_y*groesse)+((groesse_y-1)*abstand)
                    
                    taste = tk.Button(root)
                    taste["text"] = name
                    taste["command"] = partial(tastenauswertung, name, taste)
                    taste["font"] = schriftgroesse
                    taste.place(x=x_pos, y=y_pos, width=breite, height=hoehe)

    root.bind("<Key>", key_handler)
    root.mainloop()
    
if __name__ == "__main__":
    main()
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Konstanten schreibt man KOMPLETT_GROSS, man iteriert nicht über einen Index, sondern über die Werte einer Liste direkt.
Man benutzt keine nakten Excepts, in Deinem Fall sind die Exceptions durch einfache if-Abfragen auch gar nicht nötig.
Und auch kein place; oder gibt die Größe des Fensters vor, wie hier ja schon mehrfach geschrieben.
Warum setzt Du die Optionen von Eingabefeld und Button immer noch nachträglich, obwohl Dennis89 Dir schon gezeigt hat, wie es richtig geht?
`continue` versucht man zu vermeiden, Deine ›else: continue‹ ist total überflüssig und kann weg.

Das ganze könnte also so aussehen:

Code: Alles auswählen

import tkinter as tk
from functools import partial
from itertools import chain

BESCHRIFTUNG = {
    "Escape": "ESC",
    "slash": "/",
    "asterisk": "*",
    "minus": "-",
    "plus": "+",
    "Return": "=",
    "comma": ","
}
                
# zwei Tastenfelder zur Auswahl
TASTENFELD1 = [
    ["Escape","/","*","-"],
    ["7","8","9","+"],
    ["4","5","6","+"],
    ["1","2","3","="],
    ["0","0",",","="]
]

TASTENFELD2 = [
    ["ESC","ESC","*","-"],
    ["ESC","ESC","9","+"],
    ["","5","6","+"],
    ["1","2","3","="],
    ["0","0",",","="]
]

TASTENFELD = TASTENFELD2

rand = 10
abstand = 5
groesse = 50
schriftgroesse = groesse /2

def tastenauswertung(name):
    print("Eingabe", name)

def key_handler(event):
    eingabe = BESCHRIFTUNG.get(event.keysym, event.keysym)
    if eingabe in chain.from_iterable(TASTENFELD):
        tastenauswertung(eingabe)
        
def main():
    root = tk.Tk()
    root.title("Testprogramm")

    eingabefeld_wert = tk.StringVar(root, "0")
    tk.Entry(root,
        textvariable=eingabefeld_wert,
        justify=tk.RIGHT,
        state=tk.DISABLED
    ).grid(row=0, column=0, columnspan=len(TASTENFELD[0]))

    for row, tasten in enumerate(TASTENFELD):
        for column, name in enumerate(tasten):
            if name.strip() and (row==0 or TASTENFELD[row -1][column] != name) and (column==0 or TASTENFELD[row][column - 1] != name):
                rowspan = 1
                columnspan = 1
                while len(TASTENFELD[row]) > column + columnspan and TASTENFELD[row][column + columnspan] == name:
                    columnspan += 1
                while len(TASTENFELD) > row + rowspan and TASTENFELD[row + rowspan][column] == name:
                    rowspan += 1
                print(name, columnspan, rowspan)
                taste = tk.Button(root,
                    text=name,
                    command=partial(tastenauswertung, name)
                )
                taste.grid(row=row+1, column=column,
                    columnspan=columnspan,
                    rowspan=rowspan,
                    sticky=tk.NSEW
                )

    root.bind("<Key>", key_handler)
    root.mainloop()

if __name__ == "__main__":
    main()
IC-Cruncher
User
Beiträge: 4
Registriert: Dienstag 2. Mai 2023, 19:02

:shock: Hallo Sirius3,

danke für deine Antwort und deine Quelcode.
ich habe versucht den Quellcode nachzuvollziehen.
Als Anfänger etwas schwierig, aber sehr hilfreich.
ich finde es rafiniert, wir du das Tastenfeld mit den geschachtekten Schleifen aufbaust.
Beeindruckend :shock:

ich muss mich, auf jeden Fall, mich mit der .grid-Funktion und deren Parametern intensiver beschäftigen.
Ich hatte mit .place versucht, die größen und positionen der Buttons genauer definieren.

Die Anmerkung mit den Konstanden werden GROSS geschrieben, beherzige ich.
Konstanten -> GROSS
Variabeln -> klein

Die Optionen nachträglich zu schreiben, habe ich im Netz gefunden und übernommen.
Die Optionen in das Element zu schrieben, daran sollte ich mich gewöhnen.
Element = ...gestalten
Element = ...positionieren

Die Schleifen in deime Quellcode sind, wie gesagt rafiniert. Das sollte / muss ich mir mehr verinnerlichen.

continue vermeiden, danke für den hinweis, neheme ich mir an.

Das Thema auf die Tastatureingabe bzw. Tastenfeldeingabe zu reagieren, wird die nächate Hürde.

Zunächst danke an euch.
IC-Cruncher
User
Beiträge: 4
Registriert: Dienstag 2. Mai 2023, 19:02

Hallo Zusammen,

ich habe die erste Version des Taschenrechners fertig gestellt. :D
Was habe ich gelernt...ich muss noch viel lesen und ausprobieren :lol:

Der Taschenrechner reagiert auf das Tastenfeld und auf die Tastatureingabe.

Ein Thema muss ich noch lösen.
ich habe, bei der Erstellung der Buutons, jeden Button in einem Dictionary der Beschriftung zugeordnet.
Somit kann ich jeden Button im nachhinein, mit Hilfe der Beschriftung, direkt ansprechen.
Damit wollte ich den Butten blinken lassen. Egal ob die Eingabe über den Butten selbst, oder über die Tastatur erfolgt. Leider wird der Button "blau " eingefärbt und sofort wieder auf die Systemfarbe zurück gestellt. Der "Sleep"-Timer reagiert irgendwie falsch, denn die nächste Eingabe wird verzögert.

Aber zunächst bin ich zufrieden. :mrgreen:

Code: Alles auswählen

import tkinter as tk
from functools import partial
from itertools import chain
import time

BESCHRIFTUNG = {
    "Escape": "Clr",
    "slash": "/",
    "asterisk": "*",
    "minus": "-",
    "plus": "+",
    "Return": "=",
    "comma": ","
    }
                
# zwei Tastenfelder zur Auswahl
TASTENFELD = [
    ["Clr","/","*","-"],
    ["7","8","9","+"],
    ["4","5","6","+"],
    ["1","2","3","="],
    ["0","0",",","="]
    ]

ABSTAND = 1
GROESSE = 15
SCHRIFT = 12
BTN_ZUORDNUNG = {}
speicher = ""
flag = 0

def tastenauswertung(name):
    global speicher
    global flag
    
    btn = BTN_ZUORDNUNG.get(name)
    btn.config(bg="blue")
    
    if name.isdigit() == True:
        if len(eingabefeld_wert.get()) < 22:
            if flag == 1:
                eingabefeld_wert.set("0")
                flag = 0
            if eingabefeld_wert.get() == "0":
                eingabefeld_wert.set(name)
            else:
                eingabefeld_wert.set(eingabefeld_wert.get() + name)                
                
    elif name == "," and eingabefeld_wert.get().find(".")<0:
        if len(eingabefeld_wert.get()) < 22:
            if eingabefeld_wert.get() == "0":
                eingabefeld_wert.set("0.")
            else:
                eingabefeld_wert.set(eingabefeld_wert.get() + ".")
            
    elif name == "Clr":
        eingabefeld_wert.set("0")
        speicher = ""
        flag = 0
        
    elif name == "=":
        if speicher != "":
            speicher = speicher +eingabefeld_wert.get()
            eingabefeld_wert.set(eval(speicher))
            speicher = ""
            flag = 1
        
    elif name in chain.from_iterable("+-*/"):
        speicher = speicher + eingabefeld_wert.get() + name
        flag = 1

    speicheranzeige_wert.set(speicher)

    time.sleep(0.5)
    btn.config(bg="SystemButtonFace")

    
def key_handler(event):
    eingabe = BESCHRIFTUNG.get(event.keysym, event.keysym)
    if eingabe in chain.from_iterable(TASTENFELD):
        tastenauswertung(eingabe)

        
def main():
    global eingabefeld_wert
    global speicheranzeige_wert

    root = tk.Tk()
    root.title("Taschenrechner V1.0")

    speicheranzeige_wert = tk.StringVar(root, speicher)
    speicheranzeige = tk.Entry(root, textvariable=speicheranzeige_wert,
                           justify=tk.RIGHT, state=tk.DISABLED,
                           font=("arial",SCHRIFT,"bold"),
                           relief = "groove",
                           )
    speicheranzeige.grid(row=0, column=0,
                         columnspan=len(TASTENFELD[0]),
                         padx = ABSTAND, pady = ABSTAND,
                         ipadx = GROESSE
                         )
    
    eingabefeld_wert = tk.StringVar(root, "0")
    eingabefeld = tk.Entry(root, textvariable=eingabefeld_wert,
                        justify=tk.RIGHT, state=tk.DISABLED,
                        font=("arial",SCHRIFT,"bold"),
                        relief = "groove",
                        )
    eingabefeld.grid(row=1, column=0,
                     columnspan=len(TASTENFELD[0]),
                     padx = ABSTAND, pady = ABSTAND,
                     ipadx = GROESSE, ipady = GROESSE
                     )
    
    for row, tasten in enumerate(TASTENFELD):
        for column, name in enumerate(tasten):
            if name.strip() and (row==0 or TASTENFELD[row -1][column] != name) and (column==0 or TASTENFELD[row][column - 1] != name):
                rowspan = 1
                columnspan = 1
                while len(TASTENFELD[row]) > column + columnspan and TASTENFELD[row][column + columnspan] == name:
                    columnspan += 1
                while len(TASTENFELD) > row + rowspan and TASTENFELD[row + rowspan][column] == name:
                    rowspan += 1
                
                taste = tk.Button(root, text=name,
                                  command=partial(tastenauswertung, name),
                                  font=("arial",SCHRIFT,"bold"),
                                  relief = "groove"
                                  )
                taste.grid(row=row+2, column=column,
                           columnspan=columnspan, rowspan=rowspan,
                           sticky=tk.NSEW,
                           padx = ABSTAND, pady = ABSTAND,
                           ipadx = GROESSE, ipady = GROESSE
                           )
                BTN_ZUORDNUNG[name] = taste

    root.bind("<Key>", key_handler)
    root.mainloop()


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

global darf man nicht benutzen. Für alle nicht-trivialen GUIs braucht man zwingend Klassen.
Wenn man mit None nichts anfangen kann, dann benutzt man die Eckigen Klammern, um auf Wörterbücher zuzugreifen.
Mit True vergleicht man nicht explizit. Und da Du schon True und False kennst, warum gibst Du dann Deinem Flag die Werte 0 und 1?
Wenn man prüfen will, ob ein Zeichen in einer Zeichenkette vorkommt, benutzt man den in-Operator und nicht find.
`chain.from_iterable("+-*/")` ist wirklich sehr kreativ. Wie kommt man darauf?
sleep darf in einer GUI nicht vorkommen, weil sie das ganze Programm blockiert. Statt dessen benutzt man after.
Konstanten heißen Konstanten, weil sich deren Wert nicht ändert, Der Inhalt von BTN_ZUORDNUNG ändert sich aber.
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@IC-Cruncher: Die Indirektion über Zeichenketten und das es eine Funktion mit ``if``/``elif``/… gibt, die entscheidet was auf einen Tastendruck hin getan werden soll, ist unschön. Diese Funktion macht dann potentiell alles, statt eine Funktion (oder Methode) pro Aktion zu haben, was übersichtlicher und leichter zu testen ist.

Magische Zahlen wie die 22 sollten nicht mehrfach im Quelltext stehen, sondern als Konstante definiert werden. Dann lässt sich der Wert einfach ändern, und man hat einen Namen der einem verrät was diese Zahl bedeutet.

`flag` nimmt nur die Werte 0 und 1 an, sollte also eigentlich ein Wahrheitswert mit den Werten `True` und `False` sein. Zudem ist der Name zu allgemein. Das es ein Flag ist, sieht man ja an den Werten die angenommen werden, aber wofür steht das? Welche Bedeutung hat es? *Das* ist es was man mit Namen vermitteln will, wofür der Wert im Kontext des Programms steht.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten