tkinter event_generate和更新命令的意外顺序



我正在尝试让tkinter的event_generateupdate一起使用进行原子单元测试。

以下代码无法按预期工作。未打印"已生成退格事件"。我的理解是,event_generate将事件放在tkinter的事件队列中,然后update应该清除并执行队列中的所有事件。

class UpdWin(tk.Tk):
    def __init__(self):
        super().__init__()
        self.bind('<BackSpace>',
                  lambda event: print(event.keysym, 'event generated.'))

app = UpdWin()
app.event_generate('<BackSpace>')
app.update() # Update doesn't work if placed here

但是,以下代码确实打印"生成的退格事件"。事件队列中的事件将在 __init__ 期间清除并执行。在此之后,主代码将事件放入 tkinter 的队列中。

class UpdWin(tk.Tk):
    def __init__(self):
        super().__init__()
        self.bind('<BackSpace>',
                  lambda event: print(event.keysym, 'event generated.'))
        self.update()  # Update works if placed here

app = UpdWin()
app.event_generate('<BackSpace>')

我的第一个想法是,我对update命令的理解一定是错误的。Lundh、Shipman 和 TclCmd 手册页都有不同的update条目。

处理所有挂起的事件,调用

事件回调,完成任何挂起的几何管理,根据需要重绘小部件,并调用所有挂起的空闲任务。伦德 (1999(

此方法强制更新显示。希普曼(新墨西哥理工学院(

此命令用于通过重复进入事件循环来使应用程序"最新",直到处理完所有挂起的事件(包括空闲回调(。TclCmd 手册页

由此,我怀疑存在未记录的计时问题,并尝试了更新命令的第三个位置。以下代码也有效。

class UpdWin(tk.Tk):
    def __init__(self):
        super().__init__()
        self.bind('<BackSpace>',
                  lambda event: print(event.keysym, 'event generated.'))

app = UpdWin()
app.update() # Update works if placed here
app.event_generate('<BackSpace>')

为什么 tkinter 的updateevent_generate之前调用时不起作用,但在之后没有?

编辑

如果app.update位于位置 1.
,则以下代码将打印"生成的退格事件"。它将打印"FocusIn 事件生成",如果app.update位于位置 2.
注意:布莱恩·奥克利(Bryan Oakley(的回应表明,这种效应可能取决于机器。

import tkinter as tk
app = tk.Tk()
# app.update()  # Position 1
app.bind('<FocusIn>', lambda event: print('FocusIn event generated.'))
app.bind('<BackSpace>', lambda event: print(event.keysym, 'event generated.'))
app.event_generate('<BackSpace>')
app.update()  # Position 2

以下使用 when='tail' 的代码从不打印"生成的退格事件",无论app.update位于位置 1 还是位置 2。

import tkinter as tk
app = tk.Tk()
app.update()  # Position 1
app.bind('<FocusIn>', lambda event: print('<FocusIn> event generated.'))
app.bind('<BackSpace>', lambda event: print(event.keysym, 'event generated.'))
app.event_generate('<BackSpace>', when="tail")
# app.update()  # Position 2

我无法重复您的观察,所以我无法确定发生了什么。显然缺少一些代码,这也可能会受到您运行的平台以及您启动程序的方式的影响。

但是,我认为问题归结为三个因素:

  1. 默认情况下,event_generate立即处理事件的任何处理程序(即:updatemainloop本身不需要(
  2. 如果在调用event_generate之前调用update(),则不会绘制窗口,并且当未绘制窗口时,操作系统或窗口管理器不会为其提供键盘焦点。
  3. 如果应用没有键盘焦点,Tkinter 可能会忽略键盘事件。

这可能并不完全是这样 - 因为您告诉它哪个窗口将接收事件,因此重点可能不是一个考虑因素。但是,窗口的可见性可能仍然起作用。可能是tkinter逻辑说要忽略不可见窗口上的事件。也有可能您的窗口管理器/操作系统可能没有在前面运行该程序 - 键盘焦点可能位于其他应用程序上,直到您手动单击窗口(例如,这似乎是OSX上的行为(。

您可以尝试使用 when 参数来event_generate 。这允许您将处理程序的处理推迟到处理完任何其他排队事件之后。这样做,然后调用update(),也许你的代码会起作用。例如:

app.event_generate('<Backspace>`, when="tail")
app.update()

在尝试生成事件之前,还可以尝试通过调用 app.focus_force() 将应用强制置于前台。

生成

键盘事件时,您可能还希望先生成<FocusIn>事件。当您开始生成事件时,您需要确保您生成的事件的行为尽可能类似于用户生成的事件,这意味着您需要了解可见性、键盘焦点和鼠标焦点。许多年前,当我走这条路时,我记得在处理按钮时必须生成<Enter><Leave>事件,例如,因为内置绑定有时依赖于这些将按钮的状态设置为"活动"。

event_generate命令的权威文档是 Tcl/Tk 手册页。

最新更新