How to link 3 Tkinter OptionMenu lists?

Discussion in 'Python' started by Stewart Midwinter, Feb 25, 2004.

  1. 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!
    Stewart Midwinter, Feb 25, 2004
    #1
    1. Advertising

  2. Stewart Midwinter

    Peter Otten Guest

    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


    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
    Peter Otten, Feb 25, 2004
    #2
    1. Advertising

  3. Stewart Midwinter

    Eric Brunel Guest

    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
    Eric Brunel, Feb 25, 2004
    #3
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Thor

    Optionmenu.

    Thor, Sep 4, 2003, in forum: Python
    Replies:
    4
    Views:
    397
    Michael Peuser
    Sep 5, 2003
  2. Jeffrey Barish
    Replies:
    2
    Views:
    501
    Russell E. Owen
    Apr 22, 2004
  3. mariox19
    Replies:
    0
    Views:
    824
    mariox19
    Dec 30, 2004
  4. Replies:
    1
    Views:
    466
    Jeff Epler
    Apr 30, 2005
  5. =?UTF-8?B?w4FuZ2VsIEd1dGnDqXJyZXogUm9kcsOtZ3Vleg==

    List of lists of lists of lists...

    =?UTF-8?B?w4FuZ2VsIEd1dGnDqXJyZXogUm9kcsOtZ3Vleg==, May 8, 2006, in forum: Python
    Replies:
    5
    Views:
    388
    =?UTF-8?B?w4FuZ2VsIEd1dGnDqXJyZXogUm9kcsOtZ3Vleg==
    May 15, 2006
Loading...

Share This Page