Событие двойного щелчка срабатывает после каждого последующего щелчка в python tkinter - PullRequest
3 голосов
/ 14 июня 2019

Я пишу приложение, похожее на Explorer, используя список tkinter. Двойным щелчком я хочу войти в выбранную папку, чтобы очистить список и ввести содержимое папок.

Когда я щелкаю сразу после двойного щелчка, он по-прежнему считается новым двойным щелчком. Таким образом, один щелчок мышью не выполняется, то есть запись в списке не выбрана, и изменение происходит до того, как я снова дважды щелкну.

Есть ли способ «сбросить» двойной щелчок, чтобы моя программа считала следующий щелчок одним кликом, независимо от того, что я делал раньше?

Я пытался использовать координату события щелчка, чтобы получить запись «двойного щелчка», но это будет запускать при третьем щелчке вместо четвертого, что не является желательным поведением. Я также пытался связать тройной щелчок, чтобы заблокировать второй двойной щелчок, но затем программа не ответит, если я нажму более 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()

Я ожидаю, что после того, как я дважды щелкну запись, я снова смогу немедленно взаимодействовать с графическим интерфейсом, не дожидаясь, пока пройдет двойной щелчок. Кроме того, я не хочу дважды щелкать новые записи одним щелчком мыши (относительно текущего представления).

Ответы [ 4 ]

2 голосов
/ 15 июня 2019

Я бы справился с этим, создав новый класс для запоминания 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().

2 голосов
/ 15 июня 2019

Следующие данные вдохновлены ответом @ 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()
0 голосов
/ 15 июня 2019

Пытаясь реализовать решения от @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)

Это работает, потому что здесь ложный двойной щелчок можно обнаружить, проверив, если что-то выбрано, если нет, ни одного щелчка не было раньше.

0 голосов
/ 15 июня 2019

Лучший способ исправить это - иметь глобальную переменную, хранящую состояние щелчка.Поместите это в начало:

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

Затем привяжите функцию прокрутки одним щелчком мыши.Это работает, потому что: * Если пользователь дважды щелкает мышью, dclick устанавливается равным True одним щелчком мыши, то есть второй щелчок считается двойным.* Если пользователь затем щелкает один раз, флаг dclick был установлен обратно в False, что означает, что он обрабатывается как один.* Если пользователь ожидает задержки, первый щелчок сбрасывает флаг, так как он считается одним.

Это не проверено, но я надеюсь, что это поможет.

...