在 tkinter 列表框上使用 curselection(),但首先选择 returns 一个空元组

Using curselection() on a tkinter listbox, but first selection returns an empty tuple

我正在尝试使用带有来自 tkinter 的字体的列表框来更改文本字段的字体。它工作正常,除了第一次选择列表框。

第一次单击列表框时,我会收到此错误消息 IndexError: tuple index out of range,带有列表框选择的元组将是 ()。但是,下一个选择将完全正常。然后我会得到一个这样的元组 (number,) 这是什么原因?如果我绑定 <Double-Button-1> 而不是 <Button-1>,那么第一个选择(双击列表框)工作正常。

import tkinter
from tkinter import font

class EnkelTeksteditor:
    def __init__(self):
        self.hovedvindu = tkinter.Tk()
        self.tekstomraadet = tkinter.Text(self.hovedvindu, height=10, width=30)
        self.tekstomraadet.grid(column=0, row=0)
        self.scrollbar = tkinter.Scrollbar(self.hovedvindu, orient=tkinter.VERTICAL,
                                           command=self.tekstomraadet.yview)
        self.scrollbar.grid(column=1, row=0, sticky=(tkinter.N, tkinter.S))
        self.tekstomraadet.config(yscrollcommand=self.scrollbar.set)
        self.hovedvindu.title("Enkel teksteditor")

        self.fontlistbox = tkinter.Listbox(self.hovedvindu, selectmode=tkinter.SINGLE)
        self.fontene = font.families()
        for fonten in self.fontene:
            self.fontlistbox.insert(tkinter.END, fonten)
        self.fontlistbox.grid(column=3, row=0, sticky=(tkinter.N, tkinter.S))

        self.fontscroller = tkinter.Scrollbar(self.hovedvindu, orient=tkinter.VERTICAL,
                                              command=self.fontlistbox.yview)
        self.fontlistbox.config(yscrollcommand=self.fontscroller.set)
        self.fontscroller.grid(column=4, row=0, sticky=(tkinter.N, tkinter.S))


        self.fontlistbox.bind("<Button-1>", self.endre_font_listbox)
        self.fontlistbox.bind("<Key-Return>", self.endre_font_listbox)

        tkinter.mainloop()

    def endre_font_listbox(self, hendelse):
        valgte_indekser = self.fontlistbox.curselection()
        print(valgte_indekser)
        if valgte_indekser:
            font_tekst = self.fontene[valgte_indekser[0]]
            ny_font = font.Font(size=10, weight=bold_tekst, slant=italic_tekst, family=font_tekst)
            self.tekstomraadet.config(font=ny_font)


if __name__ == "__main__":
    gui = EnkelTeksteditor()

当您绑定到 <Button-1> 时,绑定的函数会在列表框有机会处理事件之前触发。这就是为什么您可以 return "break" 从绑定函数中停止事件。

在此处查看此代码:

import tkinter as tk

def return_break(event):
    return "break"

root = tk.Tk()
text = tk.Text(root)
text.pack()
text.bind("<Key>", return_break)
root.mainloop()

只要你按下一个按钮,tkinter 就会调用你的函数 return_break。该函数 returns "break" 告诉 tkinter 停止事件。这就是事件永远不会到达 text 小部件的原因。

类似地,当您绑定到 <Button-1> 时,您的函数会在列表框了解鼠标按下之前被调用。当您将它更改为 <ButtonRelease-1> 时,它起作用了,因为当您释放鼠标按钮时调用该函数并且列表框有时间处理 <Button-1> 事件。

另一种解决问题的方法是将您的代码更改为:

def endre_font_listbox(self, hendelse):
    self.fontlistbox.after(1, self._endre_font_listbox)

def _endre_font_listbox(self):
    valgte_indekser = self.fontlistbox.curselection()
    print(valgte_indekser)
    ...

这告诉 tkinter 在调用您的函数之前等待 1 毫秒(这是列表框处理事件的足够时间)(_endre_font_listbox)。

永远记住,您的函数在小部件的处理程序之前被调用。

列表框在选择发生变化时生成一个特殊的虚拟事件。您应该使用它而不是绑定到密钥。因为您要绑定到键 press,所以您的绑定会在默认绑定之前触发。导致选择更改的是默认绑定。

绑定虚拟事件的方法如下:

self.fontlistbox.bind("<<ListboxSelect>>", self.endre_font_listbox)

除了解决调用事件回调的顺序问题外,这还有一个额外的好处,即如果用户使用键盘更改选择,它也将起作用。