Default to and select first item in Tkinter listbox
Asked Answered
R

2

14

I want to automatically select the first item in the listbox. By selecting the first item, I don't mean simply defaulting to the first item or setting focus on it. I've already achieved that by doing self.listbox.select_set(0). I want the default item also selected. In other words, when I run my code below, I want print(value) to print the value of the default selection. If Asia is chosen from the optionmenu, Japan should automatically print to the console. If Africa, Nigeria should print and Germany for Europe.

Any suggestion on how I can achieve this? Thanks.

from tkinter import *
from tkinter import ttk
import tkinter.messagebox

class App:
    def __init__(self):
        self.master = Tk()
        self.di = {'Asia': ['Japan', 'China', 'Malaysia', 'India', 'Korea',
                            'Vietnam', 'Laos', 'Thailand', 'Singapore',
                            'Indonesia', 'Taiwan'],
                     'Europe': ['Germany', 'France', 'Switzerland'],
                     'Africa': ['Nigeria', 'Kenya', 'Ethiopia', 'Ghana',
                                'Congo', 'Senegal', 'Guinea', 'Mali', 'Cameroun',
                                'Benin', 'Tanzania', 'South Africa', 'Zimbabwe']}
        self.variable_a = StringVar()
        self.frame_optionmenu = ttk.Frame(self.master)
        self.frame_optionmenu.pack()
        options = sorted(self.di.keys())
        self.optionmenu = ttk.OptionMenu(self.frame_optionmenu, self.variable_a, options[0], *options)

        self.variable_a.set('Asia')
        self.optionmenu.pack()
        self.btn = ttk.Button(self.master, text="Submit", width=8, command=self.submit)
        self.btn.pack()

        self.frame_listbox = ttk.Frame(self.master)

        self.frame_listbox.pack(side=RIGHT, fill=Y)
        self.scrollbar = Scrollbar(self.frame_listbox )
        self.scrollbar.pack(side=RIGHT, fill=Y)
        self.listbox = Listbox(self.frame_listbox, selectmode=SINGLE, yscrollcommand=self.scrollbar.set)
        self.variable_a.trace('w', self.updateoptions)

        self.scrollbar.config(command=self.listbox.yview)
        self.listbox.pack()

        #Populate listbox
        for each in self.di[self.variable_a.get()]:
            self.listbox.insert(END, each)
            self.listbox.select_set(0) #This only sets focus on the first item.
        self.listbox.bind("<<ListboxSelect>>", self.OnSelect)

        self.master.mainloop()

    def updateoptions(self, *args):
        #countries = self.di[self.variable_a.get()]
        self.listbox.delete(0, 'end')
        for each in self.di[self.variable_a.get()]:
            self.listbox.insert(END, each)
            self.listbox.select_set(0) #This only sets focus on the first item.
        self.listbox.pack()

    def submit(self, *args):
        var = self.variable_a.get()
        if messagebox.askokcancel("Selection", "Confirm selection: " + var):
            print(var)

    def OnSelect(self, event):
        widget = event.widget
        value = widget.get(widget.curselection()[0])
        print(value)

App()

Running Python 3.4.1

Rambert answered 20/8, 2014 at 23:5 Comment(0)
M
27

The simplest solution is to generate the <<ListboxSelect>> event at the same time that you change the selection:

def updateoptions(self, *args):
    ...
    self.listbox.select_set(0) #This only sets focus on the first item.
    self.listbox.event_generate("<<ListboxSelect>>")
    ...
Macario answered 22/8, 2014 at 16:9 Comment(0)
A
6
# add before .mainloop()
self.listbox.selection_set( first = 0 )

EDIT#1 2014-08-21 13:50 [UTC+0000]

Tkinter.Listbox()-es have quite a complex MVC-Model-Part behaviour. Thus its Controller-Part .methods() are bit more complex subject to handle.

The Listbox() default select-mode allows only a single item to be selected, but the select-mode argument supports four settings: SINGLE, BROWSE, MULTIPLE, and EXTENDED ( the default is BROWSE ). Of these, the first two are single selection modes, and the last two allow multiple items to be selected.

These modes vary in subtle ways.

For instance, BROWSE is like SINGLE, but it also allows the selection to be dragged.

Clicking an item in MULTIPLE mode toggles its state without affecting other selected items.

And the EXTENDED mode allows for multiple selections and works like the Windows file explorer GUI—you select one item with a simple click, multiple items with a Ctrl-click combination, and ranges of items with Shift-click-s.

Multiple selections can be programmed with code of this sort:

listbox = Listbox( aWindow, bg = 'white', font = ( 'courier', fontsz ) )
listbox.config( selectmode = EXTENDED )                         # see above
listbox.bind( '<Double-1>', ( lambda event: onDoubleClick() ) ) # a lambda-wrapped CallBackHANDLER()
# onDoubleClick: get messages selected in listbox               # not listed here
selections = listbox.curselection()                             # tuple of digit-string(s), aTupleOfSTRINGs, where digit-string(s) range from { 0, 1, .., N-1 }
selections = [ int( x ) + 1 for x in selections ]               # transform string(s) to shifted int(s), make 'em { 1, 2, .., N }

When multiple selections are enabled, the .curselection() method returns a list of digit strings giving the relative numbers of the items selected, or it returns an empty tuple if none is selected.

Beware this method always returns a tuple of digit strings, even in single selection mode.

Thus symmetrically the Listbox().selection_set() method has to feature-rich so as to be able to configure all possible states for the value of <aSelectionSET>.

Q.E.D. above in the initial post.

Almeta answered 21/8, 2014 at 9:24 Comment(4)
Thank you for your answer. When posting code, please also try to explain it, so that the OP can understand the given code.Runabout
What does all of the comments related to the modes have to do with selecting the first item? This answer seems overly complex for such a simple problem.Macario
@Almeta Thanks for your explanation. I don't wish to use MULTIPLE mode at the moment. Anyway, I added self.listbox.selection_set( first = 0 ) before self.master.mainloop() as you suggested but there is no change in behavior. You can copy the modified code from ideone.com/8VzU3p and test it. Any further thoughts? Thanks.Rambert
@BryanOakley Any idea how I might solve this? Thanks.Rambert

© 2022 - 2024 — McMap. All rights reserved.