Menüauswahl mit tkinter

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.
Antworten
Benutzeravatar
Bulova
User
Beiträge: 8
Registriert: Sonntag 3. August 2025, 12:56

Hallo,

in meinem Wave-Bearbeitungsprogramm würde ich gerne ein kleines Menü hinzufügen, das das Programm steuert. Hierzu habe ich eine einfache Auswahlliste getestet:

Code: Alles auswählen

import tkinter

def ausgabe():
    lbAusgabe["text"] = liAuswahl.get("active")
    #test = lbAusgabe["text"]

def ende():
    fenster.destroy()

fenster = tkinter.Tk()
fenster.title("Bending-Methode")
fenster.resizable(0, 0)


lbAuswahl = tkinter.Label(fenster, text="Ihre Auswahl:")
lbAuswahl.grid(row=0, column=0, sticky="w", padx=5, pady=5)

frAuswahl = tkinter.Frame(fenster)
frAuswahl.grid(row=1, column=0, padx=5, pady=5)

sbAuswahl = tkinter.Scrollbar(frAuswahl, orient="vertical")
#liAuswahl = tkinter.Listbox(frAuswahl, height=4, yscrollcommand=sbAuswahl.set)
liAuswahl = tkinter.Listbox(frAuswahl, height=4, width=25, yscrollcommand=sbAuswahl.set)
sbAuswahl["command"] = liAuswahl.yview

methode = ["Methode 1", "Methode 2", "Methode 3"]
for s in methode:
    liAuswahl.insert("end", s)
liAuswahl.grid(row=0, column=0)
sbAuswahl.grid(row=0, column=1, sticky="sn")

buAusgabe = tkinter.Button(fenster, text="Ausgabe", command=ausgabe, width=10)
buAusgabe.grid(row=2, column=0, sticky="w", padx=5, pady=5)

lbAusgabe = tkinter.Label(fenster, text="(leer)")
lbAusgabe.grid(row=3, column=0, sticky="w", padx=5, pady=5)

buEnde = tkinter.Button(fenster, text="Ende", command=ende, width=10)
buEnde.grid(row=4, column=2, padx=5, pady=5)

fenster.mainloop()
print()
Was mir noch unklar ist: Wenn jetzt z. B. "Methode 2" gewählt wurde, wie kann ich im Hauptprogramm darauf zugreifen? In welcher Variablen steht dieser Text oder einfach ein Index der Liste? Ich habe bei print() schon diverse Sachen eingegeben, aber das Ausgewählte erscheint nicht.

Gruß, Dieter
Benutzeravatar
__blackjack__
User
Beiträge: 14084
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Bulova: Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Namen sollten keine kryptischen Abkürzungen enthalten. Der Name soll dem Leser vermitteln was der Wert dahinter im Programm bedeutet, nicht zum rätseln zwingen. Das `lb` `Label` und nicht `Listbox` bedeutet, kann man leicht verwechseln, auch gehen die zweibuchstabigen Kürzel ein bisschen Unter wenn gefühlt alles xyAuswahl heisst.

Man muss auch nicht alles an einen Namen binden wenn das dann später gar nicht mehr benötigt wird.

Bei Zeichenketten mit einer festen Bedeutung für Tk für die es im `tkinter`-Modul Konstanten gibt, sollte man diese Konstanten benutzen. Dann weiss man beim Lesen, dass das kein beliebiger Text ist, bei Tippfehlern bekommt man einen `NameError`, meistens schon von der IDE bevor man das ausführt, und bei nicht eindeutigen Werten wie "ns" vs. "sn" gibt es einen kanonischen Wert.

`s` sollte eigentlich `methode` heissen und `methode` sollte eigentlich `methoden`, Mehrzahl, heissen. Die Schleife kann man sich aber auch sparen und gleich mehrere Werte bei `insert()` angeben.

`ende()` ist nicht wirklich notwendig, da könnte man die Methode die aufgerufen werden soll auch gleich als `command` übergeben.

Bei Funktions- und Methodennamen ist es üblich die nach der Tätigkeit zu benennen die sie durchführen, damit der Leser weiss was sie tun und um sie leicht(er) von eher passiven Werten unterscheiden zu können. Bei Rückruffunktionen/-methoden findet man auch das Namensmuster `on_<ereignis>` wo das Ereignis beschrieben wird zu dem der Aufruf passieren soll.

Funktionen und Methoden bekommen alles was sie ausser Konstanten benötigen, als Argumente übergeben. `auswahl()` braucht als das Label und die Listbox als Argumente. Bei so simplen Programmen kommt man noch mit „Closures“ aus, zum Beispiel über `functools.partial()`, bei etwas komplexeren Programmen kommt man nicht um eine Klasse herum.

Eine Möglichleit an die aktuelle Auswahl zu kommen ist die `curselection()`-Methode, die eine Sequenz mit den ausgewählten Indexwerten liefert. Also hier entweder eine leere Sequenz wenn nichts ausgewählt ist, oder eine mit einem Element wenn ein Listeneintrag ausgewählt ist. Damit kann man dann mit `get()` den Wert abfragen.

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter
from functools import partial


def on_output(output_label, choice_listbox):
    indices = choice_listbox.curselection()
    if indices:
        output_label["text"] = choice_listbox.get(indices[0])


def main():
    window = tkinter.Tk()
    window.title("Bending-Methode")
    window.resizable(False, False)

    tkinter.Label(window, text="Ihre Auswahl:").grid(
        row=0, column=0, sticky=tkinter.W, padx=5, pady=5
    )

    frame = tkinter.Frame(window)

    scrollbar = tkinter.Scrollbar(frame, orient=tkinter.VERTICAL)
    choice_listbox = tkinter.Listbox(
        frame, height=4, width=25, yscrollcommand=scrollbar.set
    )
    scrollbar["command"] = choice_listbox.yview
    choice_listbox.grid(row=0, column=0)
    scrollbar.grid(row=0, column=1, sticky=tkinter.NS)

    frame.grid(row=1, column=0, padx=5, pady=5)

    choice_listbox.insert(tkinter.END, "Methode 1", "Methode 2", "Methode 3")

    output_button = tkinter.Button(window, text="Ausgabe", width=10)
    output_button.grid(row=2, column=0, sticky=tkinter.W, padx=5, pady=5)

    output_label = tkinter.Label(window, text="(leer)")
    output_label.grid(row=3, column=0, sticky=tkinter.W, padx=5, pady=5)

    tkinter.Button(window, text="Ende", command=window.quit, width=10).grid(
        row=4, column=2, padx=5, pady=5
    )

    output_button["command"] = partial(on_output, output_label, choice_listbox)

    window.mainloop()


if __name__ == "__main__":
    main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
Bulova
User
Beiträge: 8
Registriert: Sonntag 3. August 2025, 12:56

Die Ausführungen über Konventionen zum Programmierstil habe ich verstanden. Das Beispiel ist auch nicht von mir, sondern aus einem Buch. Wie ich auf den Index der Auswahl komme, ist immer noch unklar. Im Main z. B. eine Zeile mit print(?) im Klartext zur Anwendung der Auswahl wäre hilfreich.
Benutzeravatar
__blackjack__
User
Beiträge: 14084
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Bulova: In `on_output()` steht doch wie man an den Text kommt. Wobei das dann Datenhaltung in der GUI wäre, aber für so ein kleines Programm vielleicht noch tolerierbar.

Im `main()` kommst Du da gar nicht dran, weil vor dem `mainloop()`-Aufruf hat der Benutzer ja noch nichts auswählen können, und `mainloop()` kehrt erst zurück wenn die GUI geschlossen wurde.

Entweder macht man in der Rückruffunktion/-methode etwas mit dem Wert, oder man braucht eine Klasse wo man sich den Wert merken kann, und wo man nach dem Schliessen der GUI auf das Attribut zugreifen kann das man sich da gemerkt hat.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
Bulova
User
Beiträge: 8
Registriert: Sonntag 3. August 2025, 12:56

Ja, da muss ich mich mit der Struktur mit einer GUI noch genauer auseinandersetzen. Danke bis dahin.
Benutzeravatar
Dennis89
User
Beiträge: 1575
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

in grafischen Anwendungen passiert etwas in Abhängigkeit von einer (Benutzer-)Aktion. Wie gesagt, wenn du das Programm startest, dann wird die `main` abgearbeitet und `mainloop` ist eine Schleife, die die ganze Zeit die grafischen Elemente aktualisiert. Das heißt die läuft immer und darf nicht unterbrochen werden, sonst sieht es aus, als ob das Programm eingefroren wäre. Zur Steuerung muss man jetzt auf Ereignisse reagieren. Einem Button weißt man zum Beispiel eine Funktion zu, die ausgeführt werden soll, wenn man ihn betätigt. In diesem Fall hat man das in Zeile 45 gemacht und der Funktion noch Argumente mit übergeben. Du drückst den Button und die Funktion wird ausgeführt. Daher bringt dir ein `print` in der `main`-Funktion nichts. Wenn du sehen willst was `indices` beinhaltet, dann muss das `print` in die `on_output`-Funktion.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 14084
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ergänzend: Rückruffunktionen dürfen nur relativ kurz etwas machen, also möglichst zügig wieder zum Aufrufer — der GUI-Hauptschleife — zurückkehren. Sonst gilt auch wieder, dass die GUI einfriert. Und da gibt es dann auch Betriebssysteme/Desktop-Umgebungen die das mitbekommen und dem Benutzer einen Dialog zeigen wo drin steht, dass die Anwendung scheinbar nicht mehr reagiert und ob sie abgebrochen werden soll.

Wenn man also längere Aktionen machen will, dann muss man die in einen Arbeitssthread auslagern. Und da kommt dann dazu, das man die GUI selbst nur aus dem Thread verändern darf, in dem die `mainloop()` läuft, weil GUI-Bibliotheken in der Regel nicht thread-sicher sind.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
Bulova
User
Beiträge: 8
Registriert: Sonntag 3. August 2025, 12:56

Ja, Dank an blackjack und Dennis. Das habe ich einigermaßen verstanden. Meine Vorgänge der Datenverarbeitung könnten durchaus länger dauern, auch in Abhängigkeit von der Länge der gelesenen Datei.
Ich habe mir jetzt noch Python-Literatur bestellt, ich hoffe, dass mich das auch weiterbringt.

Gruß
Dieter
Benutzeravatar
__blackjack__
User
Beiträge: 14084
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Was man manchmal auch machen kann ist einen längeren Prozess in Aufrufe aufteilen die immer nur einen kleinen Teil der Arbeit erledigen und diese Aufrufe dann mit `Widget.after()` in die `mainloop()` verlegen.

Wo man auch dran denken sollte ist das Verhindern das während der Arbeit der Button zum starten der Arbeit noch mal gedrückt werden kann, und das man den am Ende dann wieder aktiviert.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
Bulova
User
Beiträge: 8
Registriert: Sonntag 3. August 2025, 12:56

Die Ausgabe im Terminal klappt also damit:

Code: Alles auswählen

def on_output(output_label, choice_listbox):
    indices = choice_listbox.curselection()
    if indices:
        output_label["text"] = choice_listbox.get(indices[0])
        print(indices, output_label["text"])
Darin wäre jetzt der weitere Programmcode einzugliedern unter der Prämisse, dass der Ablauf die Schleife nicht zu sehr aufhält. Andernfalls muss man eben zu anderen Maßnahmen greifen.

Bei Auswahl ergibt sich im Terminal z. B.
(1,) Methode 2

Gruß
Dieter
Antworten