Это была действительно сложная проблема, но я наконец нашел решение.После долгих поисков, многочисленных неудачных экспериментов и новых поисков я наткнулся на виртуальное событие <<MenuSelect>>
и эту ключевую строку кода: print tk.call(event.widget, "index", "active")
, на которую указал Майкл О'Доннелл, здесь .
Первая странная вещь в попытке использовать это, это то, что event.widget
в данном случае не является экземпляром объекта виджета, это строка имени пути tcl / tk, например, '. # 37759048L'.(Похоже, что это ошибка в Tkinter, так как даже другие виртуальные события, которые я тестировал -TreeviewSelect и NotebookTabChanged, включают в себя фактические экземпляры виджета, как и ожидалось.) В любом случае, строка tcl / tk может использоваться командой print tk.call(event.widget, "index", "active")
;это возвращает индекс активного в данный момент пункта меню, который огромен.
Вторая проблема, возникающая при использовании события 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()
вызывается только один раз, и только если это конкретное меню действительно будет отображаться.Метод не вызывается снова, пока все главное меню не будет оставлено и повторно открыто.Работает и при навигации по клавиатуре.