How to link 3 Tkinter OptionMenu lists?

Eric Brunel eric.brunel at N0SP4M.com
Wed Feb 25 04:15:15 EST 2004


Stewart Midwinter wrote:
> I would like to link the contents of three OptionMenu lists. When I select an
> item from the first list (call it continents), the contents of the 2nd list
> (call it countries) would update. And in turn the contents of the 3rd list (call
> it states would be updated by a change in the 2nd list.  If anyone can share a
> recipe or some ideas, I'd be grateful!

Seeing your code below, you already know everything needed to do that. Read on...

> Here's some sample code that displays three OptionMenus, but doesn't update the
> list contents :-(
> 
> ---
> #file selectSystem.py
> title = 'linked OptionMenus'
> 
> # Import Pmw from this directory tree.
> import sys
> sys.path[:0] = ['../../..']
> 
> import Tkinter
> import Pmw, re
> 
> global continentList, countryList, stateList
> 
> continentList = ['N.America','C. America', 'S. America']
> countryList   = [['Canada','USA','Mexico'],
> 		['Guatemala','Nicaragua','Panama'],
> 		['Venezuela','Colombia','Ecuador']]
> stateList     = [[['BC','Alberta','Saskatchewan','others'],
> 		['California','Oregon','Washington','others'],
> 		['Michoacan','Oaxaca','Monterrey','others']],
> 		[['Guatemala states'],['Nicaragua states'],['Panama states']],
> 		[['Venezuela states'],['Colombia states'],['Ecuador states']]]

I'd turn countryList and stateList into dictionaries like follows:

countryList   = {
   'N.America': ['Canada','USA','Mexico'],
   'C. America': ['Guatemala','Nicaragua','Panama'],
   'S. America': ['Venezuela','Colombia','Ecuador']
}
stateList = {
   'Canada': ['BC','Alberta','Saskatchewan','others'],
   'USA': ['California','Oregon','Washington','others'],
   ...
}

This is not really required, but it would make the selection of the items in the 
sub-lists far easier. See below.

> # default selection
> continentItem = continentList[0]
> countryItem   = countryList[0][0]
> stateItem     = stateList[0][0][0]

With dictionaries, that would become:

countryItem = countryList[continentItem][0]
stateItem = stateList[countryItem][0]

> class selectSystem:
>     def __init__(self, parent):
> 	# Create and pack the OptionMenu megawidgets.
> 	# The first one has a textvariable.
> 	self.var1 = Tkinter.StringVar()
> 	self.var2 = Tkinter.StringVar()
> 	self.var3 = Tkinter.StringVar()
> 	self.var1.set(continentItem)	# N. America
> 	self.var2.set(countryItem)	# Canada
> 	self.var3.set(stateItem)	# B.C.
> 
> 	self.method1_menu = Pmw.OptionMenu(parent,
> 		labelpos = 'w',
> 		label_text = 'Select Continent:',
> 		menubutton_textvariable = self.var1,
> 		items = continentList,
> 		menubutton_width = 20,
> 		menubutton_direction = 'flush',
> 		command = self._getSelection
> 	)

You should put in the command option here a method that will actually update the 
next OptionMenu from the value selected by the user. You can use the method 
setitems on it to change the list of items for the menu. Here is an example, 
using dictionaries for countryList and stateList:

def _selectContinent(self, choice):
   ## Set appropriate list of countries
   countries = countryList[self.var1.get())]
   self.method2_menu.setitems(countries)
   ## If currently selected country is not in new list, select first valid one
   if not self.var2.get() in countries:
     self.var2.set(countries[0])
   ## Set appropriate list of states
   states = stateList[self.var2.get()]
   self.method3_menu.setitems(states)
   ## If currently selected state is not in list, select first valid one
   if not self.var3.get() in states:
     self.var3.set(states[0])

> 	self.method1_menu.pack(anchor = 'w', padx = 10, pady = 10)
> 
> 	self.method2_menu = Pmw.OptionMenu (parent,
> 		labelpos = 'w',
> 		label_text = 'Select country:',
> 		menubutton_textvariable = self.var2,
> 		items = countryList[0],
> 		menubutton_width = 20,
> 		menubutton_direction = 'flush',
> 		command = self._getSelection
> 	)
> 	self.method2_menu.pack(anchor = 'w', padx = 10, pady = 10)
> 
> 	self.method3_menu = Pmw.OptionMenu (parent,
> 		labelpos = 'w',
> 		label_text = 'Select state:',
> 		menubutton_textvariable = self.var3,
> 		items = stateList[0][0],
> 		menubutton_width = 20,
> 		menubutton_direction = 'flush' ,
> 		command = self._getSelection
> 	)

Same here: you should call via the command a method updating the list of states.

> 	self.method3_menu.pack(anchor = 'w', padx = 10, pady = 10)
> 
> 	menus = (self.method1_menu, self.method2_menu, self.method3_menu)
> 	Pmw.alignlabels(menus)
> 
> 	# Create the dialog.
> 	self.dialog = Pmw.Dialog(parent,
> 	    buttons = ('OK', 'Apply', 'Cancel', 'Help'),
> 	    defaultbutton = 'OK',
> 	    title = 'Select State',
> 	    command = self.execute)
> 	self.dialog.withdraw()
> 
> 	# Add some contents to the dialog.
> 	w = Tkinter.Label(self.dialog.interior(),
> 	    text = 'Pmw Dialog\n(put your widgets here)',
> 	    background = 'black',
> 	    foreground = 'white',
> 	    pady = 20)
> 	w.pack(expand = 1, fill = 'both', padx = 4, pady = 4)
> 
>     def showAppModal(self):
>         self.dialog.activate(geometry = 'centerscreenalways')
> 
>     def execute(self, result):
> 	print 'You clicked on', result
> 	if result not in ('Apply', 'Help'):
> 	    self.dialog.deactivate(result)
> 
>     def _getSelection(self, choice):
> 	# Can use 'self.var.get()' instead of 'getcurselection()'.
> 	print 'You have chosen %s : %s : %s' % \
>             (self.var1.get(), 
> 	     self.var2.get(), 
> 	     self.var3.get() )
> 	print choice  # debug
> 	i2 = indexContinent(self.var1.get())
> 	self.var2.set(countryList[i2][0])
> 	countryItem = countryList[i2]
> 	#print pipelineItems  # debug
> 	self.method2_menu.config(items = countryList)
> 	#s3 = systemElements.indexpipe(s2,test2)

The code for this method should not be called when a menu item changes, but when 
the user validates the input. There is no need for the part starting at the call 
of indexContinent.

[snip rest of code]

A few style remarks: you should name your attributes from what they represent 
and not with numbered names like method1_menu or var3. If your class grows 
bigger, in 6 months, I'm quite sure you won't remember that var2 is the country 
(or was it the state?). Naming the attributes for the menus continentMenu, 
countryMenu and stateMenu, and the corresponding variables continentVar, 
countryVar and stateVar is a good habit to get: you'll make your programs far 
more readable and maintainable.

HTH
-- 
- Eric Brunel <eric dot brunel at pragmadev dot com> -
PragmaDev : Real Time Software Development Tools - http://www.pragmadev.com




More information about the Python-list mailing list