在 python tkinter 中每次连续单击后都会触发双击事件
Double-click event fires after every successive click in python tkinter
我正在使用 tkinter 列表框编写类似资源管理器的应用程序。双击,我想进入选定的文件夹,所以我清除列表并输入文件夹内容。
当我双击后直接点击时,仍然认为是新的双击。因此,没有执行单击,这意味着没有选择列表框条目,并且在我实际再次双击之前发生了变化。
有没有办法 "reset" 双击,这样无论我之前做了什么,我的程序都会将下一次单击视为单击?
我尝试使用点击事件坐标来获取 "double-clicked" 条目,但是这个 将 在第三次而不是第四次点击时触发,这是不希望的行为。我也试过绑定三次点击来屏蔽第二次双击,但是点击超过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 交互,而不必等待双击冷却时间过去。另外,我不想通过单击(相对于当前视图)双击新条目。
解决此问题的最佳方法是使用一个全局变量来存储点击状态。
将其放在开头:
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功能绑定到单击。
这是有效的,因为:
* 如果用户 double-clicks,dclick 被 single-click 设置为 True,这意味着第二次点击算作双击。
* 如果用户然后 single-clicks,dclick 标志被设置回 False,这意味着它被视为单个。
* 如果用户等待延迟结束,第一次点击会重置标志,因为它算作一次点击。
未经测试,但希望对您有所帮助。
我会通过创建一个新的 class 来记住 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
可能是 class 的方法。在这种情况下,函数名称应该更改,因为 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()
。
以下灵感来自@VladimirShkaberda 的回答。
FastClickListbox
是 tk.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()
在尝试实现@Vadim Shkaberda 和@Reblochon Masque 的解决方案时,我 运行 遇到了一个问题,这些解决方案对于示例来说完美无缺,但显然我把它做得太小了,无法捕捉到所有的东西,因为这些解决方案在我的项目中引入了一些新的边缘案例,例如当我以编程方式刷新列表时。
虽然我可以通过对绑定到 [=17= 的函数进行以下编辑来应用抑制错误 double-click 的想法(或更好:让它调用 single-click 功能) ]:
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)
这是有效的,因为在这里,可以通过检查是否选择了某些东西来检测错误的 double-click,如果没有,则之前没有发生过 single-click。
我正在使用 tkinter 列表框编写类似资源管理器的应用程序。双击,我想进入选定的文件夹,所以我清除列表并输入文件夹内容。
当我双击后直接点击时,仍然认为是新的双击。因此,没有执行单击,这意味着没有选择列表框条目,并且在我实际再次双击之前发生了变化。
有没有办法 "reset" 双击,这样无论我之前做了什么,我的程序都会将下一次单击视为单击?
我尝试使用点击事件坐标来获取 "double-clicked" 条目,但是这个 将 在第三次而不是第四次点击时触发,这是不希望的行为。我也试过绑定三次点击来屏蔽第二次双击,但是点击超过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 交互,而不必等待双击冷却时间过去。另外,我不想通过单击(相对于当前视图)双击新条目。
解决此问题的最佳方法是使用一个全局变量来存储点击状态。 将其放在开头:
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功能绑定到单击。 这是有效的,因为: * 如果用户 double-clicks,dclick 被 single-click 设置为 True,这意味着第二次点击算作双击。 * 如果用户然后 single-clicks,dclick 标志被设置回 False,这意味着它被视为单个。 * 如果用户等待延迟结束,第一次点击会重置标志,因为它算作一次点击。
未经测试,但希望对您有所帮助。
我会通过创建一个新的 class 来记住 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
可能是 class 的方法。在这种情况下,函数名称应该更改,因为 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()
。
以下灵感来自@VladimirShkaberda 的回答。
FastClickListbox
是 tk.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()
在尝试实现@Vadim Shkaberda 和@Reblochon Masque 的解决方案时,我 运行 遇到了一个问题,这些解决方案对于示例来说完美无缺,但显然我把它做得太小了,无法捕捉到所有的东西,因为这些解决方案在我的项目中引入了一些新的边缘案例,例如当我以编程方式刷新列表时。
虽然我可以通过对绑定到 [=17= 的函数进行以下编辑来应用抑制错误 double-click 的想法(或更好:让它调用 single-click 功能) ]:
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)
这是有效的,因为在这里,可以通过检查是否选择了某些东西来检测错误的 double-click,如果没有,则之前没有发生过 single-click。