How to link 3 Tkinter OptionMenu lists?

S

Stewart Midwinter

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!

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']]]

# default selection
continentItem = continentList[0]
countryItem = countryList[0][0]
stateItem = stateList[0][0][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
)
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
)
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)

def __call__(self):
self.dialog.show()

def indexContinent(name):
found = 'false'
for i in range(len(continentList)):
check = continentList
# print 'checking %s in %s' % (name, check) # debug
if re.search(name,check):
found = 'true'
break
print found
if (found=='true'):
#print 'index of %s is %s' % (name,i) # debug
return i
else:
return -1

def indexCountry(continentindex, name):
found = 'false'
for i in range(len(countryList[continentindex])):
check = countryList[continentindex]
# print 'checking %s in %s' % (name, check) # debug
if re.search(name,check):
found = 'true'
break
print found
if (found=='true'):
#print 'index of %s is %s' % (name,i) # debug
return i
else:
return -1


#############################

# Create selectSystem in root window for testing.
if __name__ == '__main__':
root = Tkinter.Tk()
Pmw.initialise(root)
root.title(title)

OKButton = Tkinter.Button(root, text = 'OK', command = root.destroy)
OKButton.pack(side = 'bottom')

widget = selectSystem(root)
root.mainloop()
-----



--
Stewart Midwinter
running on Mandrake Linux 9.2
PGP public key at: http://www.keyserver.net
e-mail: Stewart 'at' Midwinter.ca, stewart 'at' midtoad.homelinux.org
web: http://www.midwinter.ca, http://midtoad.homelinux.org
voice: +1.403.714.4329

Umwelt schuetzen, Rad benuetzen!
 
P

Peter Otten

Stewart said:
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

The items option is only available for OptionMenu.__init__(). To change the
items later, use setitems() instead. I suggest that you use separate
command methods for all three OptionMenu widgets. With what I think are
minimal changes:

def _getContinentSelection(self, choice):
index = continentList.index(self.var1.get())
self.method2_menu.setitems(countryList[index], 0)
self._getCountrySelection()

def _getCountrySelection(self, choice=None):
continentIndex = continentList.index(self.var1.get())
countries = countryList[continentIndex]
index = countries.index(self.var2.get())
self.method3_menu.setitems(stateList[continentIndex][index], 0)

def _getStateSelection(self, choice):
pass # your code

I think the lookup mechanism could be improved by using nested dictionaries.

Peter
 
E

Eric Brunel

Stewart said:
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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,014
Latest member
BiancaFix3

Latest Threads

Top