在python tkinter中每次连续单击后都会触发双击事件

时间:2019-06-14 20:01:11

标签: python tkinter listbox mouseevent

我正在使用tkinter列表框编写类似Explorer的应用程序。双击,我要输入选定的文件夹,因此清除列表并输入文件夹内容。

当我双击后直接单击时,仍被视为新的双击。因此,不执行任何单击操作,这意味着没有选择任何列表框条目,并且在我再次实际双击之前发生了更改。

是否有一种方法可以“重置”双击,因此无论我之前做什么,我的程序都将单击视为一次单击?

我试图使用click事件坐标来获取“双击”条目,但是此将会在第三次点击而不是第四次点击时触发,这是不希望的行为。我还尝试绑定三次单击以阻止第二次双击,但是如果我单击3次以上,并且只有在延迟之后才做出反应,该程序将不会响应。

import tkinter as tk
import random


def fill_box(event):
    """Clear and refresh listbox"""
    try:
        listbox.get(listbox.curselection()[0])
        selection = True
    except IndexError:
        selection = False
    print("Event:", event, "Selection:", selection)
    listbox.delete(0, tk.END)
    for _ in range(10):
        listbox.insert(tk.END, random.randint(0, 1000))


root = tk.Tk()
listbox = tk.Listbox(root)
for _ in range(10):
    listbox.insert(tk.END, random.randint(0, 1000))
listbox.bind("<Double-Button-1>", fill_box)
# listbox.bind("<Triple-Button-1>", lambda x: 1)  # Triple click
listbox.pack()
root.mainloop()

我的期望是,当我双击一个条目后,我可以立即再次与GUI交互,而不必等待双击冷却时间过去。另外,我也不想单击(相对于当前视图)双击新条目。

4 个答案:

答案 0 :(得分:2)

我将通过创建一个新类来记住curselection并拦截快速点击来解决此问题:

import tkinter as tk
import random


class MyListbox(tk.Listbox):
    def __init__(self, parent):
        super().__init__(parent)
        self.clicked = None


def fill_box(event):
    """Clear and refresh listbox"""
    try:
        listbox.get(listbox.curselection()[0])
        selection = True
    except IndexError:
        selection = False
        activate()                               # intercept rapid click
        return
    print("Event:", event, "Selection:", selection)
    listbox.clicked = listbox.curselection()[0]  # remember the curselection
    listbox.delete(0, tk.END)
    for _ in range(10):
        listbox.insert(tk.END, random.randint(0, 1000))

def activate():
    listbox.selection_set(listbox.clicked)


root = tk.Tk()

listbox = MyListbox(root)
for _ in range(10):
    listbox.insert(tk.END, random.randint(0, 1000))
listbox.bind("<Double-Button-1>", fill_box)
listbox.pack()

root.mainloop()

如@Reblochon Masque所述,activate可能是该类的方法。在这种情况下,应更改函数名称,因为listbox有自己的activate方法:

class MyListbox(tk.Listbox):
    def __init__(self, parent):
        super().__init__(parent)
        self.clicked = None

    def activate_clicked(self):
        self.selection_set(listbox.clicked)

并且可以称为listbox.activate_clicked()而不是activate()

答案 1 :(得分:2)

以下内容来自@VladimirShkaberda的回答。

FastClickListboxtk.Listbox的子类,它抽象了无延迟地处理快速连续双击的逻辑。这样,用户可以专注于双击触发的所需操作,而不必担心实现细节。

import tkinter as tk
import random


class FastClickListbox(tk.Listbox):
    """a listbox that allows for rapid fire double clicks
    by keeping track of the index last selected, and substituting
    it when the next click happens before the new list is populated

    remembers curselection and intercepts rapid successive double clicks
    """

    def _activate(self, ndx):
        if ndx >= 0:
            self.ACTIVE = ndx
            self.activate(ndx)
            return True
        else:
            self.selection_set(self.ACTIVE)
            self.activate(self.ACTIVE)
            return False

    def _curselection(self):
        ndxs = self.curselection()
        return ndxs if len(ndxs) > 0 else (-1,)

    def is_ready(self):
        """returns True if ready, False otherwise
        """
        return self._activate(listbox._curselection()[0])


# vastly simplified logic on the user side
def clear_and_refresh(dummy_event):
    if listbox.is_ready():
        listbox.delete(0, tk.END)
        for _ in range(random.randint(1, 11)):
            listbox.insert(tk.END, random.randint(0, 1000))


root = tk.Tk()
listbox = FastClickListbox(root)

for _ in range(random.randint(1, 11)):
    listbox.insert(tk.END, random.randint(0, 1000))

listbox.bind("<Double-Button-1>", clear_and_refresh)
listbox.pack()

root.mainloop()

答案 2 :(得分:0)

解决此问题的最佳方法是使全局变量存储单击状态。 将此放在开头:

dclick = False
def sclick(e):
    global dclick
    dclick = True #Set double click flag to True. If the delay passes, the flag will be reset on the next click

然后,将fill_box函数替换为:

    """Clear and refresh listbox"""
    global dclick
    if not dclick: #if clicked before the delay passed
        sclick(event) #treat like a single click
        return #Do nothing else
    else: #if this is an actual double click
        dclick = False #the next double is a single
    try:
        listbox.get(listbox.curselection()[0])
        selection = True
    except IndexError:
        selection = False
    print("Event:", event, "Selection:", selection)
    listbox.delete(0, tk.END)
    for _ in range(10):
        listbox.insert(tk.END, random.randint(0, 1000))

然后,将sclick函数绑定到一次单击。 之所以有效,是因为: *如果用户双击,则单击将dclick设置为True,这意味着第二次单击算作两次。 *如果用户随后单击,则将dclick标志设置回False,这意味着将其视为单个标志。 *如果用户等待延迟,则第一次单击将重置该标志,因为该标志将作为单个标志计数。

未经测试,但希望对您有所帮助。

答案 3 :(得分:-1)

在尝试实施@Vadim Shkaberda和@Reblochon Masque的解决方案时,我遇到了一个问题,这些解决方案在示例中运行得很完美,但是显然我将其最小化了以至于无法抓住所有问题,因为解决方案引入了一些新优势我的项目中的案例,例如当我以编程方式刷新列表时。

尽管我可以通过对绑定到双击的函数进行以下编辑来应用抑制虚假双击的想法(或更好的方法:使它调用单击函数):

def double_clicked(event):
    """Check if double-click was genuine, if not, perform single-click function."""
    try:
        current = self.current_val()
    except KeyError:  # False-positive Double-click
        # Simulate single click funktion by marking the currently hovered item
        index = self.listbox.index("@{},{}".format(event.x, event.y))
        self.listbox.select_set(index)
        return
    # If this is reached, a genuine Double-click happened
    functionality()

listbox.bind("<Double-Button-1>", double_clicked)

之所以起作用,是因为在这里,通过检查是否选中了某项可以检测到错误的双击,如果没有选中,则以前没有单击过。