首页 > 解决方案 > Tkinter - 事件队列和事件顺序

问题描述

我试图弄清楚事件队列是如何在 tkinter 中处理的。我发现这个链接一次,因为我玩过,但我观察到不同的行为。


我的代码的顶部是:

import tkinter as tk
from tkinter import ttk

def p_trace(*tkargs):
    return print('trace', var.get())
def p_binding(event):
    return print(event.type,var.get())

root = tk.Tk()
var = tk.IntVar(root)
var.trace_add('write',p_trace)

p_after使用 0 毫秒位于打印顺序中的不同位置。

def after_done():
    return print('after',var.get())

check3= ttk.Checkbutton(root,text='event/trace/after',
                       command=lambda t=0:root.after(t,after_done),
                       variable=var)
check3.bind('<Button-1>',p_binding)
check3.pack()

根据我找到的链接,预期的订单对我来说是这样的:

  1. 按钮按下 0
  2. 迹线 1
  3. 1 之后
  4. 按钮按下 1
  5. 跟踪 0
  6. 0后

但是如果你点击按钮,比如赌博,实际的顺序有时可能是:

  1. 按钮按下 1
  2. 跟踪 0
  3. 按钮按下 0
  4. 0后
  5. 迹线 1
  6. 1 之后

p_pseudo应该总是在顶部,但when='head'它不是,永远不会:

def p_pseudo(*tkargs):
    return print('pseudo',var.get())

check2= ttk.Checkbutton(root,text='event/trace/pseudo',
                       command=lambda:check2.event_generate('<<pseudo_event>>',when='head'),
                       variable=var)
check2.bind('<Button-1>',p_binding)
check2.bind('<<pseudo_event>>',p_pseudo)
check2.pack()

p_afterwithafter_idle也给出了奇怪的输出。

def after_done():
    return print('after',var.get())

check4= ttk.Checkbutton(root,text='event/trace/after',
                       command=lambda:root.after_idle(after_done),
                       variable=var)
check4.bind('<Button-1>',p_binding)
check4.pack()

这似乎是任意行为,并且在我的某些情况下很难拥有正确的事件顺序。我需要放慢我的程序以确保安全执行。是否可以确定 tkinter 的事件队列或不可能?

在此处输入图像描述 在此处输入图像描述

标签: pythonpython-3.xtkinterevent-handling

解决方案


要订购 tkinter 的事件队列,必须绕过所有事件并以首选顺序触发它们。或者与特定类型的事件一起使用,意味着一个特定的标志。否则 tkinters Notifier将处理如下事件的顺序。tcl 知识的人的另一种解决方案是编写一个自己的通知程序,如此处所述

Tcl_Notifier
  |
  |
  |

Tcl_DoOneEvent ------+----> get_first/oldest from Tcl_WINDOW_EVENTS (processes msg of WM)
  ^                  |
  |                  +----> get_first/oldest from Tcl_FILE_EVENTS (tk.Tk.createfilehandler)
  |                  |
  |                  +----> get_first/oldest from Tcl_TIMER_EVENTS (after)
  |                  |
  |                  +----> get_first/oldest from Tcl_IDLE_EVENTS (after_idle)
  |                  |
  |                  +----> get_first/oldest from Other event sources (virtual events)
  |                  |
  |                  +----> Tcl_SetTimer(time|maxtime) ----> Tcl_WaitForEvent ----+
  |                                                                               |
  +-------------------------------------------------------------------------------+

顺便说一句,还有一些额外的结论

  • 如果在 maxtime 之后无法调用命令 Tcl_DoOneEvent,则会出现运行时错误。

推荐阅读