【问题标题】:Double-click event fires after every successive click in python tkinter在 python tkinter 中每次连续单击后都会触发双击事件
【发布时间】:2019-10-29 11:05:18
【问题描述】:

我正在使用 tkinter 列表框编写一个类似 Explorer 的应用程序。双击时,我想进入选定的文件夹,所以我清除列表并输入文件夹内容。

当我双击后直接点击时,仍然认为是新的双击。因此,没有执行单击,这意味着没有选择列表框条目,并且在我再次真正双击之前发生了变化。

有没有办法“重置”双击,这样我的程序就会认为下一次单击是单击,无论我之前做了什么?

我尝试使用单击事件坐标来获取“双击”条目,但是此在第三次而不是第四次单击时触发,这是不希望的行为。我还尝试绑定三次单击以阻止第二次双击,但是如果我单击超过 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 交互,而无需等待双击冷却时间过去。另外,我不想通过(相对于当前视图)单击来双击新条目。

【问题讨论】:

    标签: python tkinter listbox mouseevent


    【解决方案1】:

    解决此问题的最佳方法是使用一个全局变量来存储点击状态。 把它放在开头:

    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,这意味着它被视为单次单击。 * 如果用户等待延迟,第一次点击会重置标志,因为它算作一次。

    它未经测试,但我希望它有所帮助。

    【讨论】:

      【解决方案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()

      【讨论】:

      • @ReblochonMasque 好一个。将其添加到答案中。
      【解决方案3】:

      以下内容的灵感来自@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()
      

      【讨论】:

        【解决方案4】:

        在尝试实施 @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)
        

        这是可行的,因为在这里,可以通过检查是否选择了某些内容来检测错误的双击,如果没有,则表示之前没有发生过单击。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-01-25
          • 2016-10-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-11-19
          • 1970-01-01
          相关资源
          最近更新 更多