如何为不同的Tkinter [sub-]菜单执行不同的回调?

问题描述 投票:1回答:1

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

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

我已经尝试连接到Tk.Menu的原生方法,但只需单击菜单栏项目以显示级联菜单就不会调用它们。即使.add_cascade()接受一个'command'参数,它只调用由此提供的callable,如果没有包含.add_cascade()的'menu'参数,或者它是一个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()

如果单击菜单栏上的任何位置,则会调用两个后命令回调。当你点击相应的菜单项时,我需要(并且期望)只调用其中一个。

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

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

python-2.7 tkinter callback menu submenu
1个回答
0
投票

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

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

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

print tk.call(event.widget, "index", "active")

最终结果是,每个菜单的代码通过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() 生成其内容,只调用一次,并且只有在实际显示该特定菜单时才会调用。在完成整个主菜单并重新打开之前,不会再次调用该方法。通过键盘导航时也可以工作。

© www.soinside.com 2019 - 2024. All rights reserved.