tkinter的event_generate和update命令的意外顺序

时间:2014-12-22 13:38:10

标签: tkinter unit-testing

我想让tkinter的event_generateupdate一起进行原子单元测试。

以下代码无法正常工作。 'BackSpace事件已生成。'没有打印。我的理解是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

但是,以下代码确实打印了“BackSpace事件生成”。事件队列中的事件在__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的不同条目。

  

处理所有待处理事件,调用事件回调,完成任何挂起的几何管理,根据需要重绘小部件,以及调用所有待处理的空闲任务。 Lundh (1999)

     

此方法强制更新显示。 Shipman (New Mexico Tech)

     

此命令用于通过重复进入事件循环使应用程序“更新”,直到处理完所有待处理事件(包括空闲回调)。 TclCmd man page

由此我怀疑是一个无证的计时问题,我尝试了第三个位置来进行更新命令。以下代码也有效。

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之前调用时有效,但之后没有?

修改

以下代码将打印“BackSpace事件生成”。如果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'的代码从不打印“生成的BackSpace事件”。 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 个答案:

答案 0 :(得分:2)

我无法复制您的观察结果,所以我无法肯定地说明发生了什么。显然有一些代码丢失了,这可能也会受到您运行的平台以及启动程序的影响。

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

  1. 默认情况下,event_generate会立即处理该事件的所有处理程序(即:update并且mainloop不需要本身
  2. 在调用update()之前调用event_generate 时,未绘制窗口,并且未绘制窗口时操作系统或窗口管理器不会给它键盘焦点。
  3. 如果应用没有键盘焦点,Tkinter可能会忽略键盘事件。
  4. 情况可能并非完全如此 - 因为您告诉它哪个窗口是接收事件,焦点可能不是一个考虑因素。然而,窗户的可见性仍然可以发挥作用。可能是tkinter逻辑说忽略不可见窗口上的事件。您的窗口管理器/操作系统也可能没有在前端运行程序 - 键盘焦点可能在某个其他应用程序上,直到您手动单击窗口(例如,这似乎是OSX上的行为)。 / p>

    您可以尝试将when参数用于event_generate。这使您可以处理处理程序,直到处理完任何其他排队事件。这样做,然后调用update(),也许您的代码可以正常工作。例如:

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

    在尝试生成活动之前,您可能还会尝试通过调用app.focus_force()强制将应用置于前台。

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

    event_generate命令的权威文档是Tcl/Tk man page