问题
In some cases (demo below) the value shown on the OpenMenu widget does not match that used by the program, this causes option B to be done when the user is expecting option A - causing a WTF?? reaction by the user.
Unfortunately the OptionMenu widget does not have the "command" option that I've used with other widgets to easily handle the problem (e.g. "A_Button" widget in demo). I've tried using bindings but so far I haven't a "magic bullet" that fixes the problem.
I've checked the usual places (NMT, effbot, here, etc.) and found close to no useful documention on this widget, especially when it comes to working with the items on the dropdown list. (Knowing how to determine the number of items in the list, the position/index of the currently selected value in the list and how to include the widget in the GUI's tab sequence would be useful).
My application is multilingual; when the language changes the displayed value and the dropdown list of the OptionMenu widget must change accordingly. (BTW, multilingualism means that you cannot directly use the results of .get() in the code, especially if another language gets added. To get language independence I'm using an index created by matching the .get() value to the values in the option menu - is there a better method? )
In the demo code the chosen language determines the values shown by the OptionMenu widget. "Use the date" (a Button widget) is exactly how the real application gets launched (and why it's mandatory that the GUI and program values match at all times - which is is not always happening). In contrast, "What day is it??" (also a Button widget) uses the command option to implement the expected/correct behaviour - as has been successfully done many times in my application.
To see the problem run the demo, selecting any language. Without changing languages change the day several times. Note that the printed value (used by my application) is always one selection behind that shown on the GUI widget. Next, without changing days, select a different language (the new language gets printed). The OptionMenu dropdown values do not change until after the mouse leaves the OptionMenu widget - and its displayed value never gets "translated" into the new language.
What am I overlooking/missing/doing wrong?
from tkinter import Button, IntVar, Label, OptionMenu, Radiobutton, Tk, StringVar
# You can skip over this section, it's basic gruntwork needed to set up the demo (values used have no impact on the problem).
English = 0
French = 1
Spanish = 2
DayNumbers = 3
DefaultDay = 2 # Corresponds to Tuesday, emulates the user's choice of day (on the GUI)
DefaultLanguage = English
Languages = [ "English", "French", "Spanish", "Day Numbers" ] # for use on GUI
EnglishWeekdays = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ]
FrenchWeekdays = [ "dimanche", "lundi", "mardi", "mecredi", "jeudi", "vendredi", "samedi" ]
SpanishWeekdays = [ "domingo", "lunes", "martes", "miercoles", "jeuves", "viernes", "sabado" ]
NumberedWeekdays = [ "Day 0", "Day 1", "Day 2", "Day 3", "Day 4", "Day 5", "Day 6" ]
DayNames = [ EnglishWeekdays, FrenchWeekdays, SpanishWeekdays, NumberedWeekdays ]
# The variables
LanguageInUse = DefaultLanguage
Weekdays = DayNames[ LanguageInUse ]
Today = DefaultDay # Isolates application code from language on GUI
#-------------------------------------------------------------------------------
def ChooseLanguage( ParentFrame ) :
global LanguageInUse, DropdownMenu
GUI_Language = IntVar( value = LanguageInUse )
#---------------------------------------------------------------------------
def SwitchLanguage():
global LanguageInUse , Weekdays
LanguageInUse = GUI_Language.get()
print( "SwitchLanguage sets language index to", LanguageInUse, "(" + Languages[ LanguageInUse ] + ")" )
Weekdays = DayNames[ LanguageInUse ]
DropdownMenu[ 'menu' ][ 'title' ] = Weekdays[ Today ]
for i, DayName in enumerate( Weekdays ) :
DropdownMenu[ 'menu' ].entryconfig( i )['label' ] = DayName
return
#---------------------------------------------------------------------------
LanguageButton = []
for LanguageIndex, Language in enumerate( Languages ) :
LanguageButton = LanguageButton + [ Radiobutton( ParentFrame,
indicatoron = False, width = 12,
variable = GUI_Language, command = lambda: SwitchLanguage(),
text = Language, value = LanguageIndex ) ]
LanguageButton[ LanguageIndex ].grid( row = 0 , column = LanguageIndex )
return
#-------------------------------------------------------------------------------
def GetDayIndex() :
global Today, DropdownMenu
Today = 0
for Index, DayName in enumerate( Weekdays ) :
if ( GUI_Value.get() == DayName ) :
Today = Index
break
print( "GetDayIndex sets weekday index to", Today, "(" + Weekdays[ Today ] + ")" )
for i, j in enumerate( Weekdays ) :
DropdownMenu[ 'menu' ].entryconfig( i , label = j )
return
#-------------------------------------------------------------------------------
def DoSomethingUseful() :
print( " Program uses " + str( Today ) + " (" + Weekdays[ Today ] +")" )
return
#-------------------------------------------------------------------------------
# The mainline
root = Tk()
GUI_Value = StringVar( value = Weekdays[ Today ] )
Widget1 = Label( root, text = "Today is" )
Widget1.grid( row = 1, column = 0 )
DropdownMenu = OptionMenu( root, GUI_Value, *DayNames[ LanguageInUse ] ) # NOT in TAB key sequence !!!
DropdownMenu.grid( row = 1, column = 1 )
DropdownMenu.bind( "<Leave>", lambda _ : GetDayIndex() )
#OptionMenu_Configuration( DropdownMenu )
A_Button = Button( root, text = "What day is it??", command = lambda : GetDayIndex() )
B_Button = Button( root, text = "Use the date", command = lambda: DoSomethingUseful() )
A_Button.grid( row = 1, column = 2 )
B_Button.grid( row = 1, column = 3 )
ChooseLanguage( root ) # creates/manages the language choice widgets
root.mainloop()
回答1:
Your program's logic isn't clear for me, especially '<Leave>' binding, but lets try to answer your questions in general:
Unfortunately the OptionMenu widget does not have the "command" option that I've used with other widgets to easily handle the problem (e.g. "A_Button" widget in demo).
You're wrong, because it's has that option:
import tkinter as tk
def dropdown_callback(selected=None):
print('Currently selected value is:\t%s' % selected)
root = tk.Tk()
str_var = tk.StringVar()
dropdown_menu = tk.OptionMenu(root, str_var, *['foo', 'bar', 'baz'], command=dropdown_callback)
dropdown_menu.pack()
root.mainloop()
Also, you can specify a separate command for each of your entries (rarely useful):
import tkinter as tk
def dropdown_callback(event=None):
print('Currently selected value is:\t%s' % event)
def dropdown_callback_foo():
print('Called callback is:\t%s' % dropdown_callback_foo.__name__)
def dropdown_callback_bar():
print('Called callback is:\t%s' % dropdown_callback_bar.__name__)
root = tk.Tk()
str_var = tk.StringVar()
dropdown_menu = tk.OptionMenu(root, str_var, *['foo', 'bar', 'baz'], command=dropdown_callback)
dropdown_menu._OptionMenu__menu.entryconfig(0, command=dropdown_callback_foo)
dropdown_menu._OptionMenu__menu.entryconfig(1, command=dropdown_callback_bar)
dropdown_menu.pack()
root.mainloop()
...the position/index of the currently selected value in the list...
And again, there's an option for that:
import tkinter as tk
def dropdown_callback(selected=None):
selected_index = root.tk.call(dropdown_menu.menuname, 'index', selected)
print('Currently selected value is:\t%s\t\ton position:\t%d' % (selected, selected_index))
root = tk.Tk()
str_var = tk.StringVar()
dropdown_menu = tk.OptionMenu(root, str_var, *['foo', 'bar', 'baz'], command=dropdown_callback)
dropdown_menu.pack()
root.mainloop()
...determine the number of items in the list...
It's also achievable, since number of items is just a last index + 1:
import tkinter as tk
def dropdown_callback(selected=None):
selected_index = root.tk.call(dropdown_menu.menuname, 'index', selected)
total_count = root.tk.call(dropdown_menu.menuname, 'index', 'end') + 1
print('Currently selected value is:\t%s\t\ton position:\t%d\t\twith total count:\t%d'
% (selected, selected_index, total_count))
root = tk.Tk()
str_var = tk.StringVar()
dropdown_menu = tk.OptionMenu(root, str_var, *['foo', 'bar', 'baz'], command=dropdown_callback)
dropdown_menu.pack()
root.mainloop()
From this point I think, that now your confusions with OptionMenu are solved. Except a stacking order, for sure, but you can replace your OptionMenu with ttk.Combobox at any time. There's no such <Tab>, behavior for Menu widgets, because they react differently on commands like lift. Of course, it's achievable too, but it's another question, because of many and more of "what if"'s!
That's all!
来源:https://stackoverflow.com/questions/47360766/tkinter-optionmenu-issue-bug-gui-and-program-values-not-kept-in-lockstep-py