Globale Variable oder zur Root hochhangeln oder gibt es eine andere Möglichkeit?

Fragen zu Tkinter.
Antworten
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Also, ich möchte gerne wissen, ob die mainloop schon läuft. Kann man das irgendwie abfragen?

Wenn nicht, kann man eine eigene Tk Klasse definieren, die ablegt, ob die mainloop schon gestartet ist.
Als globale Variable wäre es sehr einfach. Bei einem lokalen Attribut müßte man sich zur Root hochhangeln.

Eine Parameterweiterreichung kommt nicht in Frage. Das widerspricht dem Prinzip, dass für eine komplexe Gui die Teile unabhängig sein sollen und daher nichts weitergereicht wird.

Der Hintergrund ist folgender. Wenn man für ein Submenü, verbunden mit einer Cascade am tearoff Element etwas ändern möchte, dann kann man das mit self.entryconfig(0,**kwargs) tun.

Allerdings während des Menüaufbaues ist self.entryconfig(0,**kwargs) für das tearoff Element unwirksam. Man muss deshalb das nochmals verbunden mit after(0.01,...) nachtriggern.

Wenn die mainloop aber läuft, ist das Menü bestimmt schon aufgebaut und dieses Nachtriggern erübrigt sich. Man müße auch mal testen, ob dieses Nachtriggern auch erforderlich ist, wenn zwar das Menü schon aufgebaut ist, aber die Mainloop noch nicht läuft, oder wenn das erst funktioniert, wenn das Menü mit der Cascade verbunden ist.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Das sind die Testfälle:

Dieses geht nicht:

Code: Alles auswählen

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.config(height=100, width=400)
        # widget definitions ===================================
        self.menu = Menu_1(self)
        self['menu'] = self.menu

class Menu_1(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        # widget definitions ===================================
        self.submenu = Submenu(self)
        self.add_cascade(menu=self.submenu,label='cascade')

class Submenu(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        # widget definitions ===================================
        # tear off element
        self.entryconfig(0,background='blue')
        self.add_command(label='command1')
        self.add_command(label='command2')
        self.add_command(label='command3')
        self.add_command(label='command4')

if __name__ == '__main__':
    Application().mainloop()
Es geht erst ab dann, wenn das ganze Menü erstellt und in die App eingehängt ist:

Code: Alles auswählen

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.config(height=100, width=400)
        # widget definitions ===================================
        self.menu = Menu_1(self)
        self['menu'] = self.menu
        self.menu.submenu.entryconfig(0,background='blue')

class Menu_1(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        # widget definitions ===================================
        self.submenu = Submenu(self)
        self.add_cascade(menu=self.submenu,label='cascade')

class Submenu(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        # widget definitions ===================================
        self.add_command(label='command1')
        self.add_command(label='command2')
        self.add_command(label='command3')
        self.add_command(label='command4')

if __name__ == '__main__':
    Application().mainloop()
Nur da soll es natürlich nicht hin.

Die Lösung:

Code: Alles auswählen

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.config(height=100, width=400)
        # widget definitions ===================================
        self.menu = Menu_1(self)
        self['menu'] = self.menu

class Menu_1(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        # widget definitions ===================================
        self.submenu = Submenu(self)
        self.add_cascade(menu=self.submenu,label='cascade')

class Submenu(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        # widget definitions ===================================
        # tear off element
        self.entryconfig(0,background='blue')
        self.after_idle(lambda function = self.entryconfig : function(0,background='blue'))
        self.add_command(label='command1')
        self.add_command(label='command2')
        self.add_command(label='command3')
        self.add_command(label='command4')

if __name__ == '__main__':
    Application().mainloop()
Man setzt es, falls jemand das abfragt und setzt noch ein after_idle drauf, damit es wirksam wird.

Jedoch wenn die mainloop gestartet ist, wäre so ein after_idle überflüssig.

Die Idee ist, dass man Unzulänglichkeiten von tkinter behebt. Das wären auch try und except wegen python2 und python3.

Dieses Modul macht es möglich:

tk_extend.py

Code: Alles auswählen

try:
    from tkinter import *
    import tkinter as tk_original
except ImportError:
    from Tkinter import *
    import Tkinter as tk_original


class Menu(tk_original.Menu):

    def entryconfig(self,index,**kwargs):

        if kwargs and self['tearoff'] and not index:
            self.after_idle( lambda
                             function = tk_original.Menu.entryconfig,
                             menu = self,
                             kwargs = kwargs :
                             function(menu,0,**kwargs)
                             )
        return tk_original.Menu.entryconfig(self,index,**kwargs)
Und dann kann man das einfach so implementieren:

Code: Alles auswählen

import tk_extend as tk

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.config(height=100, width=400)
        # widget definitions ===================================
        self.menu = Menu_1(self)
        self['menu'] = self.menu

class Menu_1(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        # widget definitions ===================================
        self.submenu = Submenu(self)
        self.add_cascade(menu=self.submenu,label='cascade')

class Submenu(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        # widget definitions ===================================
        # tear off element
        self.entryconfig(0,background='blue')
        self.add_command(label='command1')
        self.add_command(label='command2')
        self.add_command(label='command3')
        self.add_command(label='command4')

if __name__ == '__main__':
    Application().mainloop()
Nur sollte natürlich nach dem Start der mainloop kein after_idle mehr getriggert werden
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Mir ist etwas eingefallen, ohne globale Variable, ohne lokale Variablen oder flags ohne hochhangeln und auch ohne after. Kommt jemand drauf?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: Ich verstehe Deine Verwunderung über das Problem nicht und auch nicht, wie Du dann darauf kommst, absurde Schlüsse daraus zu ziehen. Ein Entry kann man natürlich erst konfigurieren, wenn es existiert, also

Code: Alles auswählen

class Submenu(tk.Menu):
    def __init__(self, master, **kwargs):
        tk.Menu.__init__(self, master, **kwargs)
        self.add_command(label='command1')
        self.entryconfig(0, background='red')
        self.add_command(label='command2')
        self.add_command(label='command3')
        self.entryconfig(2, background='green')
        self.add_command(label='command4')
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Sirius3: Sorry, auch das funktioniert nicht:

Code: Alles auswählen

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk
 
class Application(tk.Tk):
 
    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.config(height=100, width=400)
        # widget definitions ===================================
        self.menu = Menu_1(self)
        self['menu'] = self.menu
 
class Menu_1(tk.Menu):
 
    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        # widget definitions ===================================
        self.submenu = Submenu(self)
        self.add_cascade(menu=self.submenu,label='cascade')
 
class Submenu(tk.Menu):
 
    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        # widget definitions ===================================
        # tear off element
        self.add_command(label='command1')
        self.entryconfig(0,background='blue')
        self.add_command(label='command2')
        self.add_command(label='command3')
        self.add_command(label='command4')
 
if __name__ == '__main__':
    Application().mainloop()
Außerdem ist auch nachtriggern mit after ist keine Lösung. Es funktioniert zwar in diesem Fall, ich denke aber, dass es in einem anderen Fall nicht funktioniert.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: mit welchem System hast Du denn getestet? Ich hab hier verschiedene *nix-Varianten, die sich alle etwas unterschiedlich verhalten (mal ist der erste Eintrag Index 0 mal Index 1, mal wird de Hintergrund gesetzt, mal der Vordergrund), aber immer, sobald der Eintrag existiert, kann ich ihn konfigurieren.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Sirius3: also ich habe einen Raspberry Pi3 mit raspbian version 8 (jessie) mit python 3.4.2 und tkinter 8.6

Das mit index 0 und 1 hängt normalerweise davon ab, ob beim Menü tearoff auf 0 oder 1 gesetzt ist.
Dass bei Dir mit background der Vordergrund gesetzt wird oder umgekehrt, darf eigentlich nicht sein.

Sollte einmal testen, ob es bei mir mit Windows auch so ist, da denke ich, dass ich dort auch 8.6 habe.
Aber das alleine ist es nicht, es kommt auch darauf an, wie gut oder schlecht die Anbindung der tkinter Version an die Python Version ist.

Oder ob die tcl Version gut mit der tk Version harmoniert.

Für meinen MK808 B mußte ich tkinter von Sourcefiles kompilieren und fuhr dann wieder von 8,6 auf 8.5 zurück - oder war es 8.4?, weil es da zuviele Ungereimtheiten gab.

Ganz extrem waren die Sashes beim PanedWindow mit meinem MK808B. Die mußte ich in einem Abstand von einer halben Sekunde triggern. Mit dem Raspberry jetzt, brauche ich das nicht, aber die Sashes beim ttk.Panedwindow brauchen geringfügig.

Ich denke mal, daß das beim entryconfig auf das tearoff Element, wohl an der tkinter Version liegt, denn es ist ein ganz sytematischer Fehler. Es geht bei mir erst, wenn das menü ganz fertig ist und mit self['menu'] = MeinMenu mit der Applikation, einem Toplevel oder einem Menubutton verbunden ist.

Ach so, foreground hat das tearoff Element nicht, nur state und background, wobei ich bei state normal, und active allerdings keinen Unterschied erkenne. disabled ist aber klar zu sehen.

Dass Vordergrund in meinem GuiDesigner nicht geht, wundert mich allerdings, sollte ich wohl mal überprüfen.

Hab es überprüft:
{'background': ('background', '', '', '', ''), 'state': ('state', '', '', <index object: 'normal'>, 'normal')}

Nur background und state!
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Sirius3: hab es mit Windows tkinter 8.6 überprüft. Da ist es genau so. Also systematisch in tkinter 8.6
Und in python2 hab ich auch tkinter 8.6
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Zusammendfassend muss ich sagen, dass das Problem lösbar ist, mann kann ja etwa in der root bei:
self['menu'] = self.mainmenu
die erforderlichen entryconfigs nachholen.

Allerdings ohne umfangreichere Infrastrukturmaßnahmen geht es nicht. Woran es zur Zeit letztendlich scheitert, ist, dass es bei menu items anscheinend keine Methode gibt, um nachzufragen, wieviele da tklinter jeweils in seiner Liste hat.

Daher müsste man die add Methoden, insert Methode und delete Methode für die Menüs mitprotokollieren, um diese Information zu gewinnen. Und das nur wegen dem entryconfig zu machen, ist wohl ziemlich übertrieben.

Aber warten wir es ab, eventuell gäbe es ja doch einen anderen und besseren Grund, so etwas dennoch zu machen.
Antworten