Dropdown menü deaktivieren

Fragen zu Tkinter.
Marvin75854
User
Beiträge: 28
Registriert: Montag 16. Juli 2018, 09:30

Montag 16. Juli 2018, 09:46

Hallo,

ich schreibe gerade meine Bachelorarbeit und muss dafür ein kleines Programm schreiben. Jedenfalls habe ich jetzt eine GUI mit relativ vielen Drop down menüs und Eingabefeldern. Besteht irgendwie die Möglichkeit, dass ich einige Eingabefelder und Dropdown menüs deaktiviere oder ausgraue, sobald ich ein anderes Dropdown menü benutze? Im Hintergrund funktioniert soweit alles. Es geht hier mehr oder weniger nur um die Übersichtlichkeit der GUI. Es soll auch nicht bei einer bestimmten Auswahl deaktiviert werden, sondern einfach sobald ich irgendetwas auswähle.

Kann man die Auswahlmöglichkeiten eines Dropdown menüs in zwei oder mehrere Kategorien unterteilen? Da ich bei mehreren Dropdown menüs teilweise 15+ Auswahlmöglichkeiten habe, wird das doch recht unübersichtlich

Gruß
Marvin
Benutzeravatar
__blackjack__
User
Beiträge: 796
Registriert: Samstag 2. Juni 2018, 10:21

Montag 16. Juli 2018, 10:03

@Marvin75854: Ja die Möglichkeit des deaktivierens besteht. Die entsprechenden Eingabeelemente haben einen 'state' den man auf `tk.DISABLED` bzw. `tk.NORMAL` setzen kann.

Menüs in Kategorien unterteilen geht ganz gut mit Untermenüs.
Global warming is caused by the sun.
Marvin75854
User
Beiträge: 28
Registriert: Montag 16. Juli 2018, 09:30

Montag 16. Juli 2018, 10:42

Danke für die schnelle Antwort. Ich verstehe aber nicht ganz wie ich das umsetzen kann. Bei normalen Buttons funktioniert es. Allerdings will ich die Menüs nicht von Anfang an deaktivieren sondern erst sobald ich ein anderes Menü benutze. Der Code ist beispielsweise:

tkvar1 = StringVar(nb.tab1)

choices1 = ("",50,100)
tkvar1.set("")

popupMenu = OptionMenu(nb.tab1, tkvar1, *choices1)
popupMenu.grid(row=24, column=15)
#
#
#
tkvar2 = StringVar(nb.tab1)

choices2 = (""150,200)
tkvar2.set("") # set the default option

popupMenu = OptionMenu(nb.tab1, tkvar2, *choices2)
popupMenu.grid(row=25, column=15)
#

Jetzt will ich entweder bei dem ersten Menü oder bei dem zweiten Menü etwas auswählen können. Das heißt sobald ich bei dem ersten 50 auswähle, soll das zweite Menü deaktiviert werden und erst wenn ich wieder "" auswähle aktiviert werden und andersrum genauso. Kann man das vielleicht mit einer If abfrage oder ähnlichem umsetzen oder gibt es da sogar eine leichtere Möglichkeit? Ich habe mittlerweile ca. 20 Menüs und nochmal genauso viele Eingabefelder die ich so irgendwie in Verbindung miteinander bringen will.
Sirius3
User
Beiträge: 8110
Registriert: Sonntag 21. Oktober 2012, 17:20

Montag 16. Juli 2018, 10:54

Die Verbindung, wann welche Felder aktiv sind und wann nicht, mußt Du entweder in einen Algorithmus, eine Datenstruktur oder viele ifs verpacken. Jedes StringVar hat einen trace-Callback, in dem man dann die Änderungen durchführen kann.
Benutzeravatar
__blackjack__
User
Beiträge: 796
Registriert: Samstag 2. Juni 2018, 10:21

Montag 16. Juli 2018, 10:54

@Marvin75854: Du kannst ja ein `command` an die `OptionMenu`-Objekte übergeben und da dann prüfen was ausgewählt wurde und entsprechend andere `OptionMenu`-Objekte (de)aktivieren. Man könnte sich eine Klasse schreiben die alle zusammengehörigen `OptionMenu`\s kennt und entsprechend dafür sorgt das alle bis auf eines eine Auswahl haben kann.

Ich hoffe das sind nicht die tatsächlichen Namen in Deinem Programm. Den Leser interessiert ja weder nur was für einen Typ der Wert hat, noch der wievielte Wert dieses Typs das ist.
Global warming is caused by the sun.
Marvin75854
User
Beiträge: 28
Registriert: Montag 16. Juli 2018, 09:30

Montag 16. Juli 2018, 12:16

OK. Trace-Callback ist einfach ein Rückgabewert? Also in meinem Fall eben das was ich in dem Menü ausgewählt habe? Ich denke ich verstehe das Vorgehen, habe aber keine Ahnung wie ich das umsetze. Wie komme ich an den trace-Callback?

Wenn ich ein command mit beispielsweise einer Funktion anhänge, passiert einfach gar nichts. Ich kann nicht mal mit print etwas ausgeben.

Doch so oder so ähnlich sind die Namen.
Ich habe halt keine Ahnung von Programmieren. Vor Jahren ein bisschen c++ gehabt und jetzt versuch ich mir das mit Google irgendwie beizubringen. Habe mir jetzt schon über 1000 Zeilen zusammenkopiert und das irgendwie hingefuscht. Learning by Doing. Habe diese Funktion mit dem Dropdown Menü irgendwo gefunden und die Namen dann einfach ein bisschen angepasst damit ich weiß was was ist und es später irgendwo anders aufrufen kann.
Benutzeravatar
__blackjack__
User
Beiträge: 796
Registriert: Samstag 2. Juni 2018, 10:21

Montag 16. Juli 2018, 12:31

Der Trace-Callback ist die Funktion bzw. der Aufruf selbiger, die Du bei dem `*Variable`-Objekt mittels der `trace()`-Methode an ein Ereignis wie Lesen oder Schreiben des Wertes in dem `*Variable`-Objekt bindest. Siehe auch The Variable Classes (BooleanVar, DoubleVar, IntVar, StringVar).

Also so ähnlich wie ein `command` beim `OptionMenu`-Objekt nur eben auf dem `*Variable`-Objekt. Wenn da bei Dir nichts passiert, dann machst Du etwas falsch. Bei mir wird bei jedem Wechsel des Wertes die Rückruffunktion mit der Auswahl als Argument aufgerufen.
Global warming is caused by the sun.
Marvin75854
User
Beiträge: 28
Registriert: Montag 16. Juli 2018, 09:30

Montag 16. Juli 2018, 13:14

Ich habe das jetzt mal mit einer If-Abfrage in der Funktion callback gemacht:
...
tkvarLF1.trace("w", callback)
...

def callback(*args):
if tkvarLF1.get() == 'Barriere - UN R95':
TW_1.config(state="disabled")
popupMenu1.config(state="disabled")

else:
TW_1.config(state="normal")
popupMenu1.config(state="normal")

Funktioniert super. Vielen Dank für eure Hilfe!

Habe mir natürlich schon Gedanken darüber gemacht was ein echter Programmierer denkt, wenn er mal einen Blick in meinen Code wirft. Habt ihr da irgendwelche Tipps was die Namen angeht? Gibt es da eine bestimmte Nomenklatur oder kann ich die benennen wie ich will, solange es möglichst übersichtlich bleibt?
Benutzeravatar
__blackjack__
User
Beiträge: 796
Registriert: Samstag 2. Juni 2018, 10:21

Montag 16. Juli 2018, 13:29

@Marvin75854: Namen sind ja für den menschlichen Leser, dementsprechend sollte der möglichst viel davon haben ihn zu lesen. Generische, durchnummerierte Namen bringen ihm da sehr wenig. Kryptische Abkürzungen auch nicht. Statt `popupMenu1` möchte der Leser beispielsweise `target_planet_menu` lesen wenn das ein Menü ist in dem man den Zielplaneten auswählen kann. Mal so als Beispiel.

Ansonsten gibt es auch noch allgemeine Richtlinien was die Namensschreibweisen angehen, die pro Sprache oder Projekt eingehalten werden sollten. Bei Python ist das: alles ausser Konstanten (KOMPLETT_IN_GROSSBUCHSTABEN) und Klassen (MixdCase) wird klein_mit_unterstrichen benannt. Siehe auch den Style Guide for Python Code.

Wenn ich mir die Namen so anschaue, in Betracht ziehe was Du über die Menge der Widgets gesagt hast, und dann sehe dass das offenbar alles auf Modulebene existiert, würde ich behaupten ein Programmierer würde, nachdem er den Schock überwunden hat, ganz schnell den Editor schliessen. ;-)
Global warming is caused by the sun.
Marvin75854
User
Beiträge: 28
Registriert: Montag 16. Juli 2018, 09:30

Dienstag 17. Juli 2018, 07:23

Ich werde es mehr berücksichtigen und mir den Link auch mal angucken. Habe als Anfänger halt einfach erst mal angefangen und alles irgendwie runtergeschrieben und versucht das Programm zum laufen zu kriegen. Muss generell den ganzen Code nochmal überarbeiten und übersichtlicher machen.
Vielen Dank für die Hilfe!
Benutzeravatar
wuf
User
Beiträge: 1481
Registriert: Sonntag 8. Juni 2003, 09:50

Dienstag 17. Juli 2018, 09:20

Hi Marvin75854

Hier eine Snippet Variante: (In plain python not optimized!)

Code: Alles auswählen

from functools import partial

import tkinter as tk

APP_TITLE = "OptionButton"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 350
APP_HEIGHT = 200


class Application(tk.Frame):

    def __init__(self, main_win):
        self.main_win = main_win
        tk.Frame.__init__(self, main_win)
        
        self.tkvar1 = tk.StringVar(main_win, "")
        self.choices1 = ("", 50, 100, 'Barriere - UN R95')
        self.tkvar1.trace("w", partial(self.callback, "option-1", self.tkvar1))

        self.popupMenu1 = tk.OptionMenu(self, self.tkvar1, *self.choices1)
        self.popupMenu1.grid(row=0, column=0)

        
        self.tkvar2 = tk.StringVar(main_win, "")
        self.choices2 = ["", 150, 200]
        self.tkvar2.trace("w", partial(self.callback, "option-2", self.tkvar2))
        
        self.popupMenu2 = tk.OptionMenu(self, self.tkvar2, *self.choices2)
        self.popupMenu2.grid(row=0, column=1)
    
    def callback(self, opt_menu, var, *args):
        value = var.get()
        if opt_menu == "option-1":
            if value == 'Barriere - UN R95':
                #TW_1.config(state="disabled")
                self.popupMenu1.config(state="disabled")
            else:
                #TW_1.config(state="normal")
                self.popupMenu1.config(state="normal")
            
        elif opt_menu =="option-2":
            print(opt_menu, value)

               
def main():
    main_win = tk.Tk()
    main_win.title(APP_TITLE)
    main_win.geometry("+{}+{}".format(APP_XPOS, APP_YPOS))
    #main_win.geometry("{}x{}".format(APP_WIDTH, APP_HEIGHT))
    
    app = Application(main_win)
    app.pack(fill='both', expand=True, padx=100, pady=100)
    
    main_win.mainloop()
 
 
if __name__ == '__main__':
    main()
Gruss wuf ;-)
Take it easy Mates!
Marvin75854
User
Beiträge: 28
Registriert: Montag 16. Juli 2018, 09:30

Freitag 20. Juli 2018, 13:36

Vielen Dank Wuf, habe deine Nachricht gerade erst gesehen. So ähnlich habe ich es jetzt auch. Sieht etwas "improvisierter" aus aber vom Prinzip her gleich und es funktioniert :D

Wo ich deinen Code jetzt gerade sehe, habe ich aber noch mal eine andere Frage. Ich verstehe nicht ganz wofür ich eine main Methode brauche. Im moment habe ich meinen Code einfach so runtergeschrieben. Die GUI mit allen Feldern und buttons usw. erstellt und die Funktionen für Berechnungen im Hintergrund und die Ausgabe der Ergebisse rufe ich immer in anderen Funktionen auf oder über verschiedene Buttons auf. Die Buttons habe ich sowieso und ich kann die Funktionen ja sowieso einfach so aufrufen. Was genau bringt mir eine main methode, abgesehen davon, dass es die ganze Sache oft wahrscheinlich einfach übersichtlicher macht?

Dann sind mittlerweile schon einige Zeilen zusammengekommen und meine GUI hat 3 tabs. Meine Idee war jetzt den Code auf mehrere Datein aufzuteilen um es übersichtlicher zu machen. Beispielsweise für jeden Tab eine neue Datei oder bestimmte Klassen ausgliedern. Allerdings habe ich woanders gelesen, dass es bei python zwar möglich ist, es aber eigentlich nicht macht, sondern alles so runterschreibt. In welchen Fällen kann man das denn trotzdem machen bzw. warum macht es keinen Sinn? Ich sehe bei meinem jetzigen Wissenstand da eigentlich keine Nachteile. Diese ganze Objekt orientierte Programmierung habe ich noch nicht ganz verstanden, habe aber das gefühl, dass wenn ich meinen Code noch verbessern kann, wird es sogar noch mehr Sinn machen einige Teile auszugliedern. Werde am Ende mit Leerzeilen und Kommentaren bestimmt auf 3500-4000 Zeilen kommen.

Gruß
Marvin
Benutzeravatar
wuf
User
Beiträge: 1481
Registriert: Sonntag 8. Juni 2003, 09:50

Freitag 20. Juli 2018, 17:26

Hi Marvin75854

Wenn hier im Forum Skriptteile platziert werden die nicht ausführbar sind bin ich gezwungen das fehlende selber zu ergänzen. Noch mühsamer wird es, wenn diese Skriptteile (Codefetzen :-)) nicht einmal in den im Editor einfach einsetzbaren Python-Code-Tags korrekt Eingerückt präsentiert werden. Für solche Fälle verwende ich mein folgendes standard Template um ein Skriptproblem auch praktisch nach vollziehen zu können:

Code: Alles auswählen

from functools import partial
import tkinter as tk

APP_TITLE = "TkTemplate_Py3_Idiom_01"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 350
APP_HEIGHT = 200


class Application(tk.Frame):

    def __init__(self, master):
        self.master = master
        tk.Frame.__init__(self, master)
           
def main():
    app_win = tk.Tk()
    app_win.title(APP_TITLE)
    app_win.geometry("+{}+{}".format(APP_XPOS, APP_YPOS))
    app_win.geometry("{}x{}".format(APP_WIDTH, APP_HEIGHT))
    
    app = Application(app_win)
    app.pack(fill='both', expand=True)
    
    app_win.mainloop()
 
 
if __name__ == '__main__':
    main()      
Da ich nicht linear (alles an einem Haufen auf Modulebene) sondern möglichst Objekt orientiert in Klassen programmiere verwende ich dieses Template. Um mehr über Objekt orientierte Programmierung zu erfahren gibt es genügend Literatur auf Papier und im Netz. Last but not least in unserem Forum mit seinen insgesamt mehr als 323118 Beiträgen. Da musst du dich schon selber schlau zu machen. Die in meinem Template verwendeten Main-Teile musst du natürlich nicht unbedingt so übernehmen. Solange du dein linear geschriebenes Skript mit bis zu 4000 Zeilen jetzt und in einem halben Jahr noch problemlos überblicken kannst sollte es für dich ja in Ordnung sein.

Gruss wuf ;-)
Take it easy Mates!
Benutzeravatar
__blackjack__
User
Beiträge: 796
Registriert: Samstag 2. Juni 2018, 10:21

Freitag 20. Juli 2018, 22:07

@Marvin75854: Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Keine Variablen. Wenn das Hauptprogramm also in einer Funktion und nicht auf Modulebene stehen soll, braucht die einen Namen. Üblich ist `main()`. Und die wird dann nicht einfach so aufgerufen, sondern nur wenn der Namen des Moduls (`__name__`) den Wert '__main__' hat. Und das ist nur der Fall wenn das Modul als Programm ausgeführt wird. Es ist *nicht* der Fall wenn man das Modul importiert. In einem anderen Modul oder in einer Python-Shell. Das heisst Du kannst das dann importieren, ohne dass das Programm ausgeführt wird. Aber Du kannst dann einzelne Funktionen aufrufen, Exemplare von Klassen erstellen und so weiter.

Das kann von anderen Modulen aus sinnvoll sein, um automatisierte Tests durchzuführen, oder Funktionen und Klassen in einem anderen Programm zu verwenden.

Und einige Werkzeuge und Bibliotheken erwarten auch das man Module ohne das da irgendwas passiert, importieren kann. Das Standardwerkzeug zur Dokumentation (Sphinx) zum Beispiel wenn man das verwendet um Docstrings direkt aus den Modulen in die Dokumentation zu übernehmen. Oder die `multiprocessing`-Bibliothek.

Man teilt auch in Python grössere Programme in mehrere Module auf. Nur nicht so extrem wie einige Leute, die von anderen Sprachen kommen das machen, also nicht grundsätzlich eine Klasse pro Modul.

3,5 bis 4 KLOC sind schon recht viel. Das würde ich aufteilen.

Kommentare gibt es hoffentlich nicht deswegen viele weil die Namen so schlecht sind‽

Eine gute Achse an der man ein GUI-Programm aufteilen kann ist Programmlogik und GUI. Das hilft auch falsche Abhängigkeiten zu erkennen, denn wenn das Modul mit der Programmlogik etwas aus dem Modul mit der GUI importieren soll, dann hat man etwas falsch gemacht.
Global warming is caused by the sun.
Marvin75854
User
Beiträge: 28
Registriert: Montag 16. Juli 2018, 09:30

Dienstag 24. Juli 2018, 09:03

Ja man findet zum Programmieren glücklicherweise extrem viele Infos, ist nur teilweise trotzdem nicht so leicht zu verstehen. Habe ja erst vor 2 Wochen angefangen.
Ich verstehe den Unterschied zwischen Funktionen und Klassen einfach noch nicht so richtig. Ich habe bei mir bisher erst eine Klasse und 4 Unterklassen davon definiert und für mich ist das einfach nur eine Ansammlung von Funktionen. Ich habe eine If Funktion erstellt und je nach dem was auf der GUI eingegeben wird, wird eine andere Unterklasse erstellt und Funktionen daraus aufgerufen. Es wäre doch aber auch möglich diese Funktionen einfach ohne Klasse zu definieren und aufzurufen oder nicht?

Also für mich wäre es egal, allerdings schreibe ich meine Bachelorarbeit nicht nur für den Professor, sondern auch für ein Unternehmen. Die verstehen von Programmieren zwar genauso wenig wie ich, aber es kann ja sein, dass da trotzdem irgendwann mal jemand in den Code guckt. Es ist im Grunde genommen nur wichtig, dass das Programm läuft. Wenn ich dann am Ende noch Zeit habe, kann ich es immer noch optimieren. Ich versuche nur einfach trotzdem schon nicht zu viel zu "pfuschen" und das einigermaßen vernünftig aufzubauen.

Also meine Idee wäre ein Hauptprogramm zu erstellen in dem die main Funktion eine GUI mit allen Buttons und Eingabefeldern usw. erstellt und dann eben die Klassen und Funktionen für die Berechnungen aus anderen Modulen importiere.

Ich habe zwischen den einzelnen Funktionen und Teilen des Programms immer mehrere Kommentarzeilen eingefügt mit einer Überschrift. Einfach damit ich die entsprechende Stelle schneller finden kann. Ich habe einige Namen noch etwas angepasst. Die Abkürzungen die ich benutze machen hier schon Sinn, zumindest für mich.

__blackjack__ hat geschrieben:
Freitag 20. Juli 2018, 22:07

Eine gute Achse an der man ein GUI-Programm aufteilen kann ist Programmlogik und GUI. Das hilft auch falsche Abhängigkeiten zu erkennen, denn wenn das Modul mit der Programmlogik etwas aus dem Modul mit der GUI importieren soll, dann hat man etwas falsch gemacht.
Diesen Satz verstehe ich nicht so ganz. Ich muss doch in dem Modul mit der Programmlogik etwas aus der GUI importieren, da ich auf der GUI ja Eingabe- und Auswahlfelder habe und die jeweiligen Werte für die Berechnung und Ausgabe der Ergebnisse brauche. Oder was ist mit Programmlogik gemeint? Oder meinst du damit nur, dass ich in dem Modul mit der GUI die Funktionen aufrufe und damit nur die Variablen übergebe, aber nichts importiere, bzw. nur die Funktion in dem Modul mit der GUI importiere.



Ich habe noch eine ganz andere Frage. Ich gebe auf der GUI verschiedene Sachen ein, führe im Hintergrund eine Berechnung durch und gebe dann Werte auf der GUI aus und schreibe diese in einem bestimmten Format in einer Datei raus. Jetzt würde ich gerne diese rausgeschriebene Datei zusammen mit einem Fahrzeugmodel oder einer Simulationsdatei in einem anderen Programm öffnen. Diese Datei will ich über die GUI manuell auswählen können. Ich arbeite unter LINUX. Ich erwarte natürlich nicht, dass ihr mir hier jetzt den CODE schreibt aber gibt es da irgendwelche Funktionen in python oder habt ihr Stichworte nach denen ich suchen kann? Ich habe da bisher nichts gefunden was ich so bei mir anwenden konnte.
Antworten