Python tkinter绑定:如何防止双重事件

4

我有一个使用tkinter和按键绑定的Python程序。程序调用外部程序,然后询问如何处理它,期望按下n键表示“否”或y键表示“是”。问题在于:如果我按两次允许的键,则第二个键将被存储并稍后处理 - 因此适用于错误的问题。

import tkinter as tk
import time

class App:
    counter = 0

    def __init__(self, master): # Constructor
        # build GUI
        self.label = tk.Label(text="Press <space>", width=40)
        self.label.grid(row=1, column=1, sticky='w')

        # bind keys to buttons
        master.bind('<Key-space>', self.keyPressed)
        pass # __init__


    def keyPressed(self, event):
        print("Step 1")
        self.counter = self.counter + 1
        self.label.configure(text = str(self.counter))
        self.label.update()
        time.sleep(3)
        print("Step  2")
        pass # Key1Pressed
# End of class App

root = tk.Tk()
root.option_add('*font', ('Arial', 11, 'bold'))
# root.attributes("-toolwindow", 1)

posX = root.winfo_screenwidth() - 500
posY = 30
root.geometry("+%d+%d" % (posX, posY))

root.title("Bind tester")
display = App(root)
root.mainloop()

在上述代码片段中,我用 sleep() 函数替换了外部程序,并更改了空格键绑定。如果启动 python3 脚本并在休眠期间连续按两次空格键,则会在终端中看到输出结果,如下所示:
Step 1
Step  2
Step 1
Step  2

我希望在睡眠过程中忽略所有引发的按键事件。所以我试用信号量,但是也没有成功。问题可能更加复杂,因为如果我在睡眠期间连续按下空格键三次,会得到以下结果:
Step 1
Step  2
Step 1
Step 1
Step  2
Step  2

看起来函数keypressed在同时被调用多次。是否有可能在接受新事件之前清除事件队列?

2个回答

5
这需要使用Tkinter的一个小技巧,即必须从函数中返回“break”。以下程序将键重新绑定到返回“break”的“ignore()”函数。我不明白这是如何或为什么,它在http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm进行了记录,并且确实有效。
import sys
if sys.version_info[0] < 3:
    import Tkinter as tk     ## Python 2.x
else:
    import tkinter as tk     ## Python 3.x

class App:
    def __init__(self, master): # Constructor
        self.master=master
        self.counter=0
        # build GUI
        self.label = tk.Label(text="Press <space>", width=40)
        self.label.grid(row=1, column=1, sticky='w')

        # bind keys to buttons
        master.bind('<Key-space>', self.keyPressed)


    def ignore(self, event):
        print "ignore"
        return "break"

    def keyPressed(self, event):
        self.master.bind('<Key-space>', self.ignore)
        print("Step 1")
        self.counter = self.counter + 1
        self.label["text"] = str(self.counter)
        self.master.after(3000, self.bindit)
        print("Step  2")

    def bindit(self):
        self.master.bind('<Key-space>', self.keyPressed)
        print("Step 3 = ready for more input")

root = tk.Tk()
root.option_add('*font', ('Arial', 11, 'bold'))
# root.attributes("-toolwindow", 1)

posX = root.winfo_screenwidth() - 500
posY = 30
root.geometry("+%d+%d" % (posX, posY))

root.title("Bind tester")
display = App(root)
root.mainloop()

1
“如何”和“为什么”的解释非常简单。请参阅以下答案进行了解:https://dev59.com/QWgu5IYBdhLWcg3wJT5I#11542200 - Bryan Oakley
谢谢,你的解决方案有效。现在我必须在我的代码中实现你的建议。我必须使用“after”来完成我的方法,并将其他事件重定向到一个带有“break”的方法。也许我还可以尝试使用信号量在原始的keyPressed方法中实现它。 - DirkS
在运行外部程序之前,您只需将键重新绑定到ignore()。在子进程(或您使用的任何其他方法)返回后,您可以将键重新绑定到keyPressed()。您可以消除after(),因为它只是模拟外部程序运行的时间。 - user4171906
1
感谢您的解决方案。在我的情况下,执行return "break"就足以防止tkinter.Entry小部件在插入新文本以进行自动完成功能后自动选择文本。 - tom_mai78101

1

after方法也是一样的,但是为了澄清:实际上我不使用sleep。相反,我通过subprocess.call调用外部程序,并等待它完成工作。我需要一个函数来控制事件队列。 - DirkS
这里没有什么神秘的地方 - time.sleep 不会“干扰”事件循环,它只是阻止其运行。 - Bryan Oakley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接