Menüauswahl abfragen

Fragen zu Tkinter.
Benutzeravatar
wuf
User
Beiträge: 1483
Registriert: Sonntag 8. Juni 2003, 09:50

Samstag 23. Dezember 2017, 10:55

Hier eine etwas entflochtene und ausführbare Variante des Beitrages:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from functools import partial

try:
    # Tkinter for Python 2.xx
    import Tkinter as tk
except ImportError:
    # Tkinter for Python 3.xx
    import tkinter as tk

APP_TITLE = "Menu Abfragen"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 400
APP_HEIGHT = 300


class Application(tk.Frame):

    def __init__(self, master):
        self.master = master
        tk.Frame.__init__(self, master)

        self.obj_canvas_master = tk.Canvas(master, width=500, height=300,
            relief="sunken", bd=3)
        self.obj_canvas_master.pack()

        self.obj_frame = tk.Frame(self.obj_canvas_master, background="blue",
            bd=6, relief="groove", width=300, height=200, padx=3, pady=3)
            
        self.obj_canvas_master.create_window(0, 0, window=self.obj_frame,
            anchor="nw", tags="self.frame")

        # attach popup to canvas
        self.obj_frame.bind("<Button-1>", self.popup)
        
        # create a popup menu
        mainmenu = tk.Menu(self.obj_frame)
        menu = tk.Menu(mainmenu, tearoff=0, relief="raised", bd=3,
            activeforeground="red", activebackground="yellow")
        mainmenu.add_command(label="Spieler neu", command=self.hello)
        
        submenu_player_edit = tk.Menu(menu, tearoff=0)
        submenu_player_edit.add_command(label="Name ändern", command=self.hello)
        submenu_team_choice = tk.Menu(submenu_player_edit, tearoff=0)

        for i in range(1, 17):
            '''
            #~~ Variante OP
            #   Funktioniert: Typischer gewöhnungsbedürftige 'lambda' Syntax 
            submenu_team_choice.add_command(label="Team " + str(i), 
                command=lambda i=i: self.auswahl(str(i)))
                
            #~~ Variante sanfu
            #   Funktioniert nicht: Hier wird immer der letzte Team-Index
            #   als Argument übergeben 
            submenu_team_choice.add_command(label="Team {}".format(i),
                command=lambda: self.auswahl(i))
            '''
            #~~ Variante Sirius3, __deets__ (und wuf)
            #   Funktioniert: Ein etwas mehr verständlicherer 'partial' Syntax
            submenu_team_choice.add_command(label="Team {}".format(i),
            command=partial(self.auswahl, i))
            
        submenu_player_edit.add_cascade(label="Team wählen", menu=submenu_team_choice)
        submenu_player_edit.add_command(label="Handycap wählen", command=self.hello)
        menu.add_cascade(label="Spieler anpassen", menu=submenu_player_edit)

        submenu_player_move = tk.Menu(menu, tearoff=0)
        submenu_player_move.add_command(label="hoch", command=self.hello)
        submenu_player_move.add_command(label="runter", command=self.hello)
        menu.add_cascade(label="Spieler verschieben", menu=submenu_player_move,
            state=tk.DISABLED)

        submenu_player_delete = tk.Menu(menu, tearoff=0)
        submenu_player_delete.add_command(label="ja", command=self.hello)
        submenu_player_delete.add_command(label="nein", command=self.hello)
        menu.add_cascade(label="Spieler löschen", menu=submenu_player_delete)

        menu.add_separator()
        menu.add_command(label="Statistik", command=self.hello)
        menu.add_separator()
        menu.add_command(label="abbrechen")

        self.menu = menu
        
    def popup(self, event):
        self.menu.post(event.x_root, event.y_root)
        #self.menu.post(100, 100)
    
    def hello(self):
        print("hello!")
       
    def auswahl(self, number):
        print("Dies ist Team {}".format(number))

           
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))
        
    Application(app_win).pack(fill='both', expand=True)
    
    app_win.mainloop()

 
if __name__ == '__main__':
    main()      
Gruss wuf :wink:
Take it easy Mates!
Sirius3
User
Beiträge: 8428
Registriert: Sonntag 21. Oktober 2012, 17:20

Samstag 23. Dezember 2017, 10:56

@snafu: hier noch mal das Problem als Programm, mit dem es hoffentlich klar wird:

Code: Alles auswählen

from functools import partial
 
def define_functions1():
    result = []
    for i in range(5):
        result.append(lambda: print(i))
    return result

def define_functions2():
    result = []
    for i in range(5):
        result.append(partial(print, i))
    return result

def main():
    func1 = define_functions1()
    func2 = define_functions2()
    print("Ergebnis 1: ")
    for f in func1: f()
    print("Ergebnis 2: ")
    for f in func2: f()
    
if __name__ == '__main__':
    main()
und der Ausgabe:
[codebox=text file=Unbenannt.txt]
Ergebnis 1:
4
4
4
4
4
Ergebnis 2:
0
1
2
3
4
[/code]
Benutzeravatar
snafu
User
Beiträge: 5536
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Samstag 23. Dezember 2017, 12:57

Habe es jetzt begriffen, denke ich. Die GUI führt die Lambdas nicht unmittelbar aus. Dadurch gilt bei einem nachträglichen Aufruf nur der letzte Wert von i. Ähnlich wie es wäre, wenn man die Lambdas zuvor in eine Liste ablegt und die Liste anschließend durchläuft.
shcol (Repo | Doc | PyPi)
Benutzeravatar
snafu
User
Beiträge: 5536
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Samstag 23. Dezember 2017, 13:11

__deets__ hat geschrieben:Das Problem ist, das der Closure/ABschluss über die Namen passiert. Nicht über die Werte.
Jetzt nochmal gelesen und nun auch verstanden. Hatte etwas länger gedauert bei mir.
shcol (Repo | Doc | PyPi)
suk
User
Beiträge: 17
Registriert: Sonntag 17. Dezember 2017, 01:18

Sonntag 24. Dezember 2017, 13:10

Das war bis hier recht ausführlich. Danke, konnte einiges Neues mitnehmen.
Als Fazit für mich ... beide Varianten sind möglich.
Vorteil lambda: direkter Zusammenbau der erforderlichen Funktion
Vorteil partial: deutlich einfacher zu verstehen, jedoch Import einer zusätzlichen Funktion erforderlich

Habe allerdings noch ein zweites Problem. Wie bekomme ich es hin, dass das Menü nicht gleich bei Auswahl geschlossen wird. Die Menüpunkte hoch und runter sollen ggf. mehrmals hintereinander ausgeführt werden. Da wäre es nervig, jedesmal den Menüpunkt neu aufrufen zu müssen.
Mein Ziel wäre es bei konsequenter Menüführung, das Menü bei einigen Auswahlpunkten offen zu halten oder nach Ausführung genau so wieder aufzumachen, dass der Benutzer sofort auf dem selben Menüpunkt steht.
Die Alternative wäre natürlich, die Mehrfachwiederholung in der Funktion selber einzubauen.
Hätte jemand eine Idee?
__deets__
User
Beiträge: 3499
Registriert: Mittwoch 14. Oktober 2015, 14:29

Sonntag 24. Dezember 2017, 16:02

Das geht denke ich nicht. Du hast dir da eine Interaktion überlegt für die Menüs nicht gedacht sind. So etwas baut man in einen normalen Dialog.
suk
User
Beiträge: 17
Registriert: Sonntag 17. Dezember 2017, 01:18

Dienstag 26. Dezember 2017, 01:42

wuf hat geschrieben:Hier eine etwas entflochtene und ausführbare Variante des Beitrages:

...
class Application(tk.Frame):
...
@wuf: Deine version sieht sehr aufgeräumt aus. Ich habe aber anscheinend noch nicht ganz verstanden, warum Du in die Klassendefinition tk.Frame aufgenommen hast. Ich hätte ja vermutet, dass an der Übergabe app_win auch die Funktion tk.Frame mit dranhängt.
Welche Aufgabe hat der Teil in der Klassendefinition?
Sirius3
User
Beiträge: 8428
Registriert: Sonntag 21. Oktober 2012, 17:20

Sonntag 31. Dezember 2017, 09:55

@suk: das nennt sich Vererbung und hat was mit Objektorientierung zu tun. Application erbt alle Eigenschaften von tk.Frame und bekommt noch ein paar mehr.
Benutzeravatar
wuf
User
Beiträge: 1483
Registriert: Sonntag 8. Juni 2003, 09:50

Sonntag 31. Dezember 2017, 12:29

suk hat geschrieben:@wuf: Deine version sieht sehr aufgeräumt aus. Ich habe aber anscheinend noch nicht ganz verstanden, warum Du in die Klassendefinition tk.Frame aufgenommen hast. Ich hätte ja vermutet, dass an der Übergabe app_win auch die Funktion tk.Frame mit dranhängt:
Als Basis meiner Experimentierskripte verwende ich folgendes Template um einzelne im Beitrag gesammelte Codefetzen in eine ausführbare Struktur einzufügen. Der Klassenname 'Application' müsste eventuell eher in 'Container' umbenannt werden. Übergeben wird nur das Hauptfenstersobjekt ohne ein Frameobjekt übergeben.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
from functools import partial
 
try:
    # Tkinter for Python 2.xx
    import Tkinter as tk
except ImportError:
    # Tkinter for Python 3.xx
    import tkinter as tk
 
APP_TITLE = "Template"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 400
APP_HEIGHT = 300
 
 
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))
       
    Application(app_win).pack(fill='both', expand=True)
   
    app_win.mainloop()
 
 
if __name__ == '__main__':
    main()      
Gruss wuf :wink:
Take it easy Mates!
__deets__
User
Beiträge: 3499
Registriert: Mittwoch 14. Oktober 2015, 14:29

Sonntag 31. Dezember 2017, 12:51

Ich würde die die Vererbung von Frame loswerden. Sie ist unnötig und schlimmstenfalls verwirrend. Sie würde nur dann Sinn ergeben, wenn durch die Ableitung die eigentliche Darstellung innerhalb tkinters verändert würde. So wie man zb in Qt ein QWidget mit überladen der Paint Methode machen kann.

Tkinter kennt so etwas meiner Kenntnis nach nicht, höchstens könnte man ein Styling vornehmen - das kann man aber immer auch in dem umliegenden Code machen.
Benutzeravatar
wuf
User
Beiträge: 1483
Registriert: Sonntag 8. Juni 2003, 09:50

Sonntag 31. Dezember 2017, 17:40

__deets__ hat geschrieben:Ich würde die die Vererbung von Frame loswerden. Sie ist unnötig und schlimmstenfalls verwirrend. Sie würde nur dann Sinn ergeben, wenn durch die Ableitung die eigentliche Darstellung innerhalb tkinters verändert würde. So wie man zb in Qt ein QWidget mit überladen der Paint Methode machen kann.
Wie würdest du dann das obige Template abändern, dass es deinen Vorstellungen entspricht?
Gruss wuf :wink:
Take it easy Mates!
__deets__
User
Beiträge: 3499
Registriert: Mittwoch 14. Oktober 2015, 14:29

Sonntag 31. Dezember 2017, 19:13

Einfach von object erben, und den Frame im Konstruktor erzeugen. Grob (auf dem Telefon getippt):

Code: Alles auswählen

class Application(object):

     der __init__(self, ...):
           self._frame = tk.Frame(....)
Benutzeravatar
wuf
User
Beiträge: 1483
Registriert: Sonntag 8. Juni 2003, 09:50

Sonntag 31. Dezember 2017, 23:09

OK __deets__

Danke für deine Rückantwort. Habe mein Templates für Tkinter-Versuche wie folgt auf meine Bedürfnisse abgeändert:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from functools import partial

try:
    # Tkinter for Python 2.xx
    import Tkinter as tk
except ImportError:
    # Tkinter for Python 3.xx
    import tkinter as tk

APP_TITLE = "NewTemplate_01"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 300
APP_HEIGHT = 200

        
class Application(tk.Tk):

    def __init__(self):
        
        tk.Tk.__init__(self)
        self.title(APP_TITLE)
        self.protocol("WM_DELETE_WINDOW", self.close_app)
        self.geometry("+{}+{}".format(APP_XPOS, APP_YPOS))
        self.geometry("{}x{}".format(APP_WIDTH, APP_HEIGHT))

        self.option_add("*Button.highlightThickness", 0)
        
        # Trial container-01
        self.app_container_01 = tk.Frame(self, bg='yellow', bd=0)
        self.app_container_01.pack(side='left', fill='both', expand=True)
        tk.Button(self.app_container_01, text='Container-01').pack(expand=True)

        # Trial container-02
        self.app_container_02 = tk.Frame(self, bg='steelblue', bd=0)
        self.app_container_02.pack(side='left', fill='both', expand=True)
        tk.Button(self.app_container_02, text='Container-02').pack(expand=True)
        
    def close_app(self):
        # Here do something before apps shutdown
        print("Good Bye!")
        self.destroy()
        
Application().mainloop()
P.S. Nicht auf der Telefon-Konsole getippt. Wünsche dir und allen Forum Mitgliedern noch alles Gute fürs 2018!

Gruss wuf :wink:
Take it easy Mates!
__deets__
User
Beiträge: 3499
Registriert: Mittwoch 14. Oktober 2015, 14:29

Montag 1. Januar 2018, 12:23

Frohes neues zurück.

Auch hier hast du ja aber eine Vererbung drin, diesmal halt zu tk.Tk. Wozu? Einen solchen Schritt sollte man nur machen, wenn es notwendig ist. nicht nur, weil es zulässig ist.
Benutzeravatar
wuf
User
Beiträge: 1483
Registriert: Sonntag 8. Juni 2003, 09:50

Montag 1. Januar 2018, 19:18

OK __deets__

Dann hältst du diese Variante für besser?

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from functools import partial

try:
    # Tkinter for Python 2.xx
    import Tkinter as tk
except ImportError:
    # Tkinter for Python 3.xx
    import tkinter as tk

APP_TITLE = "NewTemplate_01"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 300
APP_HEIGHT = 200

        
class Application(object):

    def __init__(self, app_win):
        self.app_win = app_win
        app_win.protocol("WM_DELETE_WINDOW", self.close_app)
        
        # Trial container-01
        self.app_container_01 = tk.Frame(app_win, bg='yellow', bd=0)
        self.app_container_01.pack(side='left', fill='both', expand=True)
        tk.Button(self.app_container_01, text='Container-01').pack(expand=True)

        # Trial container-02
        self.app_container_02 = tk.Frame(app_win, bg='steelblue', bd=0)
        self.app_container_02.pack(side='left', fill='both', expand=True)
        tk.Button(self.app_container_02, text='Container-02').pack(expand=True)
        
    def close_app(self):
        # Here do something before apps shutdown
        print("Good Bye!")
        self.app_win.destroy()

        
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_win.option_add("*Button.highlightThickness", 0)
    
    Application(app_win)
    
    app_win.mainloop()
 
 
if __name__ == '__main__':
    main()
Gruss wuf :wink:
Take it easy Mates!
Antworten