Getting a Callback When a Tkinter Listbox Selection Is Changed

Getting a callback when a Tkinter Listbox selection is changed?

You can bind to the <<ListboxSelect>> event. This event will be generated whenever the selection changes, whether it changes from a button click, via the keyboard, or any other method.

Here's a simple example which updates a label whenever you select something from the listbox:

import tkinter as tk

root = tk.Tk()
label = tk.Label(root)
listbox = tk.Listbox(root)
label.pack(side="bottom", fill="x")
listbox.pack(side="top", fill="both", expand=True)

listbox.insert("end", "one", "two", "three", "four", "five")

def callback(event):
selection = event.widget.curselection()
if selection:
index = selection[0]
data = event.widget.get(index)
label.configure(text=data)
else:
label.configure(text="")

listbox.bind("<<ListboxSelect>>", callback)

root.mainloop()

screenshot

This event is mentioned in the canonical man page for listbox. All predefined virtual events can be found on the bind man page.

Why does my selection callback get called on my second Listbox?

It is because the event fires when the first list box loses the selection when you click on the other list box. The event fires whenever the selection changes, not just when it is set.

If you don't want the first listbox to lose its selection when you click in the second listbox, set exportselection to False for the listboxes. Otherwise, tkinter will only allow one to have a selection at a time.

Tkinter ListboxSelect callback always processes last item in the list

It was indeed tricky to achieve what You've asked for.

Default behavior of Listbox is to select last item when it gains focus.

To fool it, You can add an empty word as a last item of Your words list.
This will make it invisible in a Listbox.
Then in Your process_item callback, check if selected value is empty (optionally if it's last value as well). If so, remove focus from Listbox and return from Your callback. This will look as if nothing happened.

Here is modified code:

from tkinter import *

class simple(Frame):
def __init__(self, parent=None):
Frame.__init__(self)
self.master.title('DEMO')
self.master.bind('<Control-q>', quit)
self.pack()

self.create_widgets()

def create_widgets(self):
# Words with dummy "" word in the end
words = ['An', '"empty"', 'selection', 'processes', 'the', 'last', 'item', 'in', 'this', 'list', ""]

self.lb = Listbox(self, width=12, height=25, selectmode=SINGLE, exportselection=False)
self.lb.pack(side=LEFT)
self.lb.bind('<<ListboxSelect>>', self.process_item)

Label(self, anchor=W, text='\n\nClick here\n\n<--\n\nin the emtpy space...').pack(side=RIGHT)

self.lb.insert(0, *words)

def process_item(self, event):
selection = self.lb.curselection()
item = self.lb.get(selection)
if item == "":
# It's last dummy item, remove focus and return
self.master.after(0, self.master.focus)
return

# Real item was clicked
self.do_stuff_with_item(item)
self.lb.delete(selection)

def do_stuff_with_item(self, item):
print(item)

def engage():
root = Tk()
sw = simple(root)
sw.pack()
root.mainloop()

if __name__ == '__main__':
engage()

Calling a function based on a Listbox current selection curselection() in Tkinter

The listbox will fire the virtual event <<ListboxSelect>> whenever the selection changes. If you bind to it, your function will be called whenever the selection changes, even if it was changed via the keyboard.

Bind callback to selection change on Tkinter Text widget

According to the manual (in the section "The Selection"):

Whenever the sel tag range changes a virtual event <<Selection>> is generated.

so binding the widget for this event should do this for you.

I know you tagged the question for python use, but it is well worth checking the Tk documentation for the details on using the Tk widgets.

python tkinter listbox event binding

listbox nearest item is found by y, not x.

 self.nearest(event.x)     # wrong
self.nearest(event.y) # right

Update: I didn't notice the real problem first:

    listbox = Listbox(frame)

It's not the same listbox which you subclassed, it's another unrelated listbox. Your listbox (which is make_list) is empty, that's why it always returns -1 for nearest.

Perhaps subclassing a frame is a good idea (anyway, better than subclassing listbox and adding a frame with another listbox into it). Then you'll have to bind event on that real listbox which is not empty.

Quick way to see how it will work when fixed is to call nearest of a real listbox with event.widget:

self.curIndex = event.widget.nearest(event.y)

How to assign a command or callback to a tkinter listbox

Yes, you can bind <<ListboxSelect>> to the list box.

listbox = tk.Listbox(root)
listbox.bind("<<ListboxSelect>>", callback)

tkinter Listbox loses its selection when clicking elsewhere on the form

You are doing it wrong. You need to use the config (or configure) method:

lb.configure(exportselection=False)


Related Topics



Leave a reply



Submit