I’ve encountered an unusual problem in a script that I’m working on. The program is written in Python3/Tkinter. It initializes two Listboxes, wrapped in two separate Frames, and according to the user’s commands displays either one or the other. An exception arises when the user selects on one of the items in the first Listbox, then decides to change screen and selects an item in the second frame.
I’ve created a sample code (below), to replicate the problem. With this code, during runtime, clicking on one item in the first Listbox (white background) makes the second Listbox appear (orange background). Also, the first object is “unpacked” and thus the user should no longer be able to interact with it. Finally, clicking on an item in the second Listbox, raises an IndexError in the “on_select_1” method from the first object. Why would that method execute after that event?? I’m well aware of my limits as a programmer, but I’m pretty sure this is not what is supposed to happen.
import tkinter as tk
class Listbox1(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.lb1 = tk.Listbox(self)
for i in ["item 1.1", "item 1.2", "item 1.3", "item 1.4"]:
self.lb1.insert(tk.END, i)
self.lb1.bind("<<ListboxSelect>>", self.on_select_1)
self.lb1.pack()
def on_select_1(self, *args):
try:
print("item {} selected in listbox 1".format(self.lb1.curselection()[0]))
except IndexError:
print("IndexError raised in method 'on_select_1'")
self.master.switch()
class Listbox2(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.lb2 = tk.Listbox(self, bg='orange')
for i in ["item 2.1", "item 2.2", "item 2.3", "item 2.4"]:
self.lb2.insert(tk.END, i)
self.lb2.bind("<<ListboxSelect>>", self.on_select_2)
self.lb2.pack()
def on_select_2(self, *args):
print("item {} selected in listbox 2".format(self.lb2.curselection()[0]))
class App(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.pack()
self.lb1_frame = Listbox1(self)
self.lb2_frame = Listbox2(self)
self.lb1_frame.pack()
def switch(self):
self.lb1_frame.pack_forget()
self.lb2_frame.pack()
def main():
root = tk.Tk()
app = App(root)
app.mainloop()
if __name__ == '__main__':
main()
By default, tkinter only allows one widget to hold the selection at a time. Thus, when you select something in your second listbox, the item selected in the first listbox is deselected. That causes your function to be called. When this happens, self.lb1.curselection()[0])
throws an error because the selection is empty.
A simple solution that allows the selection to remain unchanged in the first listbox when you select something in the second listbox is to set the exportselection
option to False
for both listboxes.
self.lb1 = tk.Listbox(self, exportselection=False)
...
self.lb2 = tk.Listbox(self, bg='orange', exportselection=False)
Based on this answer respective the comment on it from @BryanOakley:
The event doesn't represent a click, the event represents "the current item has changed" which may not always have an x and a y (ie: if you use the keyboard to change the current selection). event.widget.curselection() is what you should use.
So if the Listbox is destroyed, the new selection is as per the documentation
.curselection()
Returns a tuple containing the line numbers of the selected element or elements, counting from 0. If nothing is selected, returns an empty tuple. .
Therefore you get the IndexError as self.lb1.curselection()==()
, which results in ()[0]
.
来源:https://stackoverflow.com/questions/48991760/unexpected-behavior-with-python3-tkinter-and-two-listboxes-bound-to-listboxsel