【问题标题】:How can I execute different callbacks for different Tkinter [sub-] Menus?如何为不同的 Tkinter [sub-] 菜单执行不同的回调?
【发布时间】:2019-09-09 06:02:11
【问题描述】:

我有一个带有主菜单栏的 Tkinter GUI,使用 Tkinter 的 Menu 小部件。我想在发布子菜单之前执行代码(通过.add_cascade() 级联的另一个菜单项),以便我可以在显示之前动态更改其内容。我使用Menu 的 postcommand 参数进行了这项工作,但我注意到使用它的效率非常低;当点击 anyone 子菜单时,会调用 all 子菜单的 postcommand 回调,而不仅仅是为具有回调而创建的特定子菜单。即使点击没有菜单项的菜单栏也会执行所有回调,即使没有创建子菜单。

这是来自 Menu 模块及其 postcommand 参数的预期行为吗?我不明白为什么在为下拉菜单创建单独的 Menu 实例后仍然会发生这种情况。

我已经尝试使用 Tk.Menu 的本地方法,但是当简单地单击菜单栏项以调出级联菜单时,它们都不会被调用。即使 .add_cascade() 接受一个“命令”参数,它也只会在不包含 .add_cascade() 的“菜单”参数或者如果它是一个 lambda 表达式(这两者都导致没有子菜单)时调用它提供的可调用对象单击该项目时显示)。 (您可以使用下面的test() 函数看到这一点。)

这是一个显示此行为的简单应用:

import Tkinter as Tk
import time


def test(): print 'test'

class firstMenu( Tk.Menu ):

    def __init__( self, parent, tearoff=False ):
        Tk.Menu.__init__( self, parent, tearoff=tearoff, postcommand=self.repopulate )

    def repopulate( self ):
        print 'repopulating firstMenu'
        time.sleep( 2 ) # Represents some thinking/processing

        # Clear all current population
        self.delete( 0, 'last' )

        # Add the new menu items
        self.add_command( label='Option 1.1' )
        self.add_command( label='Option 1.2' )


class secondMenu( Tk.Menu ):

    def __init__( self, parent, tearoff=False ):
        Tk.Menu.__init__( self, parent, tearoff=tearoff, postcommand=self.repopulate )

    def repopulate( self ):
        print 'repopulating secondMenu'
        time.sleep( 2 ) # Represents some thinking/processing

        # Clear all current population
        self.delete( 0, 'last' )

        # Add the new menu items
        self.add_command( label='Option 2.1' )
        self.add_command( label='Option 2.2' )


class Gui( object ):

    def __init__( self ): # Create the TopLevel window
        root = Tk.Tk()
        root.withdraw() # Keep the GUI minimized until it is fully generated
        root.title( 'Menu Test' )

        # Create the GUI's main program menus
        menubar = Tk.Menu( root )
        menubar.add_cascade( label='File', menu=firstMenu( menubar ), command=test )
        menubar.add_cascade( label='Settings', menu=secondMenu( menubar ) )
        root.config( menu=menubar )

        root.deiconify() # Brings the GUI to the foreground now that rendering is complete

        # Start the GUI's mainloop
        root.mainloop()
        root.quit()


if __name__ == '__main__': Gui()

如果您单击菜单栏上的任意位置,则会调用两个 postcommand 回调。当您单击相应的菜单项时,我需要(并且希望)只调用其中一个。

我不确定它是否相关,但我也在另一个小部件上使用与上下文菜单相同的菜单项。所以他们的 .post() 方法也需要能够在菜单显示之前触发同样的回调。

如果您有任何见解,请提前致谢。

【问题讨论】:

    标签: python-2.7 tkinter callback menu submenu


    【解决方案1】:

    这是一个非常棘手的问题,但我终于找到了解决方案。经过大量搜索、无数次失败的实验和更多搜索,我发现了虚拟事件<<MenuSelect>> 和这一关键代码行:print tk.call(event.widget, "index", "active"),由 Michael O' Donnell 指出,here

    关于尝试使用它的第一个奇怪的部分是 event.widget 在这种情况下不是小部件对象的实例,它是一个 tcl/tk 路径名称字符串,例如'.#37759048L'。 (这似乎是 Tkinter 中的一个错误,因为即使是我测试过的其他虚拟事件 -TreeviewSelect 和 NotebookTabChanged- 也包括实际的小部件实例,正如预期的那样。)无论如何,print tk.call(event.widget, "index", "active") 命令可以使用 tcl/tk 字符串;返回当前活动菜单项的索引,这是巨大的。

    使用 MenuSelect 事件带来的第二个问题是在正常遍历菜单时会多次调用它。单击一个菜单项会调用它两次,将鼠标移动到相邻的菜单项,或者将鼠标移动到子菜单然后返回主菜单项,也会调用它两次。离开菜单也可以。但这可以通过向 Menu 类添加一个标志和向事件处理程序添加一些逻辑来很好地清理。这是完整的解决方案:

    import Tkinter as Tk
    import time
    
    
    class firstMenu( Tk.Menu ):
    
        def __init__( self, parent, tearoff=False ):
            Tk.Menu.__init__( self, parent, tearoff=tearoff )
            self.optionNum = 0 # Increments each time the menu is show, so we can see it update
            self.open = False
    
        def repopulate( self ):
            print 'repopulating firstMenu'
    
            # Clear all current population
            self.delete( 0, 'last' )
    
            # Add the new menu items
            self.add_command( label='Option 1.' + str(self.optionNum+1) )
            self.add_command( label='Option 1.' + str(self.optionNum+2) )
            self.optionNum += 2
    
    
    class secondMenu( Tk.Menu ):
    
        def __init__( self, parent, tearoff=False ):
            Tk.Menu.__init__( self, parent, tearoff=tearoff )
            self.optionNum = 0 # Increments each time the menu is show, so we can see it update
            self.open = False
    
        def repopulate( self ):
            print 'repopulating secondMenu'
    
            # Clear all current population
            self.delete( 0, 'last' )
    
            # Add the new menu items
            self.add_command( label='Option 2.' + str(self.optionNum+1) )
            self.add_command( label='Option 2.' + str(self.optionNum+2) )
            self.optionNum += 2
    
    
    class Gui( object ):
    
        def __init__( self ): # Create the TopLevel window
            self.root = Tk.Tk()
            self.root.withdraw() # Keep the GUI minimized until it is fully generated
            self.root.title( 'Menu Tests' )
    
            # Create the GUI's main program menus
            self.menubar = Tk.Menu( self.root )
            self.menubar.add_cascade( label='File', menu=firstMenu( self.menubar ) )
            self.menubar.add_cascade( label='Settings', menu=secondMenu( self.menubar ) )
    
            self.root.config( menu=self.menubar )
            self.root.deiconify() # Brings the GUI to the foreground now that rendering is complete
    
            # Add an event handler for activation of the main menus
            self.menubar.bind( "<<MenuSelect>>", self.updateMainMenuOptions )
    
            # Start the GUI's mainloop
            self.root.mainloop()
            self.root.quit()
    
        def updateMainMenuOptions( self, event ):
            activeMenuIndex = self.root.call( event.widget, "index", "active" ) # event.widget is a path string, not a widget instance
    
            if isinstance( activeMenuIndex, int ):
                activeMenu = self.menubar.winfo_children()[activeMenuIndex]
    
                if not activeMenu.open:
                    # Repopulate the menu's contents
                    activeMenu.repopulate()
                    activeMenu.open = True
    
            else: # The active menu index is 'none'; all menus are closed
                for menuWidget in self.menubar.winfo_children():
                    menuWidget.open = False
    
    
    if __name__ == '__main__': Gui()
    

    最终结果是每个菜单的代码通过.repopulate() 生成其内容只被调用一次,并且只有当该特定菜单实际要显示时才被调用。在整个主菜单离开并重新打开之前,不会再次调用该方法。通过键盘导航也可以使用。

    【讨论】:

      猜你喜欢
      • 2023-04-07
      • 1970-01-01
      • 1970-01-01
      • 2022-01-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-04
      相关资源
      最近更新 更多