【问题标题】:Unexpected behavior with Tkinter and two Listboxes bound to ListboxSelect eventTkinter 和绑定到 ListboxSelect 事件的两个列表框的意外行为
【发布时间】:2020-06-13 00:59:19
【问题描述】:

我在正在编写的脚本中遇到了一个不寻常的问题。该程序是用 Python3/Tkinter 编写的。它初始化两个列表框,分别包裹在两个单独的框架中,并根据用户的命令显示一个或另一个。当用户在第一个 Listbox 中选择一个项目,然后决定切换屏幕并在第二个框架中选择一个项目时,会出现异常。

我创建了一个示例代码(如下)来复制问题。使用此代码,在运行时,单击第一个列表框(白色背景)中的一个项目会使第二个列表框出现(橙色背景)。此外,第一个对象是“解包”的,因此用户应该不再能够与其交互。最后,单击第二个 Listbox 中的项目,会在第一个对象的“on_select_1”方法中引发 IndexError。为什么该方法会在该事件之后执行?我很清楚自己作为程序员的局限性,但我很确定这不是应该发生的。

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()

【问题讨论】:

  • 你做了什么来调试这个?索引越界错误是不言自明的。在尝试从中取出物品之前,您是否检查过self.lbl.curselection() 返回的内容?可能不是你想的那样。
  • 嗨,Brian,尊敬的,我认为你没有抓住重点......超出范围的索引确实很容易捕捉,但不言自明的是为什么第二个 Listbox 对象会调用该方法在第一个列表框中。绑定正确完成,因此在第二个列表框中选择一个项目应该只运行它自己的“on_select_2”方法。它怎么会最终在另一个对象中调用“on_select_1”方法?

标签: python python-3.x tkinter listbox


【解决方案1】:

基于此answer 各自来自@BryanOakley 的评论:

该事件不代表单击,该事件代表“当前项目已更改”,它可能并不总是有 x 和 y(即:如果您使用键盘更改当前选择)。 event.widget.curselection() 是你应该使用的。

所以如果 Listbox 被销毁,新的选择是根据 documentation

.curselection()
返回一个包含所选元素的行号的元组,从 0 开始计数。如果未选择任何内容,则返回一个空元组。 .

因此,您得到的 IndexError 为self.lb1.curselection()==(),结果为()[0]

【讨论】:

  • ...但是为什么要执行该方法?它超出了它的范围,不是吗?我知道第一个列表框的“curselection()”返回 None,但是当这种情况发生时,我的第二个对象不应该调用第一个对象的 curselection()。
  • 解压列表框时,它的curselection是什么?
  • pack_forget() 之后,curselection()[0] 返回我在第一个列表框中选择的任何项目的索引值(触发切换方法以显示第二个列表框)。
  • 你能告诉我们一些关于你的环境的事情吗?我无法在 python35-33 和 python27 中重现该错误。 (都在窗户上)
  • 我在Win10上工作,解释器是Python 3.6。
【解决方案2】:

默认情况下,tkinter 一次只允许一个小部件保存选择。因此,当您在第二个列表框中选择某些内容时,会取消选择在第一个列表框中选择的项目。这会导致您的函数被调用。发生这种情况时,self.lb1.curselection()[0]) 会抛出错误,因为选择为空。

当您在第二个列表框中选择某些内容时,允许第一个列表框中的选择保持不变的简单解决方案是将两个列表框的exportselection 选项设置为False

self.lb1 = tk.Listbox(self, exportselection=False)
...
self.lb2 = tk.Listbox(self, bg='orange', exportselection=False)

【讨论】:

  • 嗨@Bryan Oakley,这是仅在某些版本的tkinter / python中的“默认”吗?为什么在使用完全相同代码的所有版本/条件下都不会出现错误?
  • @R4PH43L:AFAIK 这在过去的一两年里没有改变,在所有平台上都是一样的。我目前没有可以测试的 windows 框,所以我不能明确地说这种行为在现代版本的 windows 上是相同的。如果它不一样,我会感到惊讶。
  • 是的.. 这解决了问题。谢谢!事实上,effbot.org link 上有报道。我昨天在研究过程中遇到了那篇文章,但没有把这些点联系起来。对不起。
  • @Bryan Oakley:我无法在运行 python35-33 / python27 的 Win7 上重现错误。
【解决方案3】:

这个问题肯定存在于 Windows 10 上的 Python 3.8 中,即使将我的两个列表框之一更改为 export=FALSE 也解决了这个问题。

【讨论】:

    猜你喜欢
    • 2018-07-18
    • 1970-01-01
    • 2020-06-05
    • 2013-01-06
    • 2020-09-16
    • 1970-01-01
    • 2020-11-27
    • 1970-01-01
    相关资源
    最近更新 更多