【问题标题】:Controlling Order of Callbacks with Tkinter使用 Tkinter 控制回调顺序
【发布时间】:2014-08-25 02:01:14
【问题描述】:

短版:有没有办法控制在 Tkinter 中处理与不同小部件关联的回调的顺序?

加长版:在测试和学习 Tkinter 时,我一直在 IDLE 中使用以下程序:

import Tkinter
guiRoot = Tkinter.Tk()
hWindow = Tkinter.Frame(guiRoot)
hWindow.grid(); x = 0; y = 0
et1 = Tkinter.Entry(hWindow)
et2 = Tkinter.Entry(hWindow)

def ut(x, y, event):
    print "X",x,", Y:",y
    print "Args:", 
    print "Widget:",event.widget.get()
    print

def Tkquit():
    print "Leaving program..."

et1 = Tkinter.Entry(hWindow)
et2 = Tkinter.Entry(hWindow)
eb = Tkinter.Button(hWindow, command=Tkquit, text="Send")
et1.grid(column=x, row=y)
et1.bind("<FocusOut>", lambda event, x1=x, y1=y:ut(x1, y1, event))
y = y + 1; et2.grid(column=x, row=y)
et2.bind("<FocusOut>", lambda event, x1=x, y1=y:ut(x1, y1, event))
y = y + 1
eb.grid(column=x, row=y)
guiRoot.mainloop()

当我从一个 Entry 字段移动到另一个时,会调用 ut()。当我点击按钮时,我收到消息“Leaving program...”(稍后将在例程中退出代码),但没有来自刚刚失去焦点的文本小部件的消息。

这引出了两个问题:

1) 为什么没有调用 Entry 字段的回调?

此外,如果我希望该按钮退出应用程序,我还暗示我希望其他回调在来自该按钮的回调之前完成。所以:

2) 如何控制 Tkinter 调用回调的顺序?

【问题讨论】:

    标签: python callback tkinter


    【解决方案1】:

    按下退出时没有调用&lt;FocusOut&gt; 事件的原因是入口小部件不会失去焦点。默认情况下,当您单击 Tkinter 按钮时,它们不会获得焦点。但是,如果您使用 ttk.Button,它确实会窃取焦点,因此将调用您的回调。我个人认为这是按钮的 ttk 实现中的一个错误,但多年来一直如此。

    在任何一种情况下,您都可以为按钮添加绑定,当您单击它们时,它们会窃取焦点。这样做将导致任何&lt;FocusOut&gt; 事件在先前具有焦点的小部件上触发。通常这是不需要的,但 Tkinter 为您提供了这种灵活性。

    例如,将此添加到您的代码中,以便在您单击按钮时触发 &lt;FocusOut&gt; 事件:

    # force the button to steal focus when clicked
    eb.bind("<1>", lambda event: eb.focus_set())
    

    您无法控制事件的处理顺序,因为事件是按照它们发生的顺序处理的。对于一个工具包做任何其他事情都是错误的。我想你会发现,一旦你对 Tkinter 的工作原理有了更好的理解,你就不需要乱序处理事件了。

    【讨论】:

    • 在创建小部件时有什么办法可以做到这一点,使用“command=callback”或使用 lambda 进行回调并引用正在创建的小部件?这不是必需的,但有时在一行中完成所有操作会更容易。
    • @Tango:我不明白你的问题。是的,您可以将小部件传入,尽管发送到回调的事件对象已经具有该信息。我个人认为让按钮窃取焦点是一个坏主意,所以我不想鼓励这种行为。我的猜测是,除了更改按钮的默认行为之外,还有一种更好的方法可以解决您认为遇到的任何问题。
    • 我的意思是如果你使用 "Tkinter.Button(command=lambda a, b, c, stuff=thing:callback(stuff, a, b, c)" 有什么办法可以还添加一个参数来引用该命令中正在创建的对象?换句话说,“Tkinter.Button(command=lambda a, b, c, stuff=thing, item=thiswidget:callback(stuff, item, a, b, c)”,其中“thiswidget”指的是正在创建的 Entry 小部件。(不必稍后使用 bind 来完成。)(它可以是按钮或列表或其他东西。)
    • @Tango:如果要传入按钮对象,必须分两步创建。创建小部件,然后使用.configure() 方法添加回调。但除此之外,您可以在回调中传递您想要的任何内容。不过这很不寻常——可能有更好的方法来实现你的真正目标,不管是什么。
    • 我猜我脑子里只是学究式的好奇心。我对 Python 还是很陌生,而且我经常会在其中发现一些出乎我意料的技巧,所以我只是想知道是否可以通过某种方式做到这一点。谢谢!
    【解决方案2】:

    我认为您在这里解决了错误的问题。当您使用具有回调的库时,您就是该库的完全怜悯。除非 Tkinter 为您提供更改此选项的选项,否则您无法选择何时调用函数(如果有的话)或调用它们的顺序。很可能它们与 Tkinter 的内部实现(即“硬连线”)高度耦合,因此无法轻易更改。

    如果您的程序固有地依赖于按特定顺序触发的事件,那么您已经为您的程序引入了脆弱性的来源。在极少数情况下,这可能是必要的(例如,可能出于效率原因),但在大多数情况下,它只是将您的程序与库不必要的耦合。如果库决定更改稍后触发事件的顺序怎么办?如果用户发现导致您的逻辑中断的事件组合怎么办?如果稍后添加一些东西并在逻辑中引入错误怎么办?

    要使您的界面和逻辑稳健,请尝试以这样一种方式设计您的程序,即调用处理程序的顺序不会对程序行为产生任何影响。在可能的情况下,尽量保留您的函数 idempotent 以避免程序中的状态组合爆炸。

    在您的情况下,如果您希望在触发按钮之前执行某个操作,请考虑调用所述操作以确保它确实发生,无论 Tkinter 是否发出 FocusOut 事件。

    【讨论】:

      【解决方案3】:

      短版:

      是的。

      加长版:

      使用统一的 callbackHANDLER> 进程,在 Tkinter 小部件的公共事件路由接口(调用方(..., command = callbackHANDLER> ), aTkWidgetINSTANCE>.bind( &lt;&lt;_anEventTYPE_&gt;&gt;, callbackHANDLER > ) 或 aTkStateVARIABLE>.trace_variable( { "w" | "r" | "u" }, callbackHANDLER> ))。

      加上设计适当的中间层应用程序逻辑,以接收所有事件->在其中路由,捕捉调用者的上下文和堆栈/重新排序/处理传入的回调请求流按照您希望其处理出现的顺序。

      Tkinter 在每个事件的实例变量中都有很多相关的细节,因此您可以完全控制每个调用/每个事件。

      Edit#1 2014-08-25 07:00

      command 设置非常有可能使用带有可选实例包装的 lambda 容器:

      command = ( lambda  deltaMinutes = 1,       # sets lambda-container to use a var.
                          aWidget      = self.B1, # sets lambda-container to ref aWidget->B1
                          anotherVar   = 0:
                          self.DateTimeNOW_MODEL_Add( deltaMinutes )
                       or                        # trick to work with a sequence of commands
                          self.LogPROCESS( "Step back  1x M1", anotherVar )
                       )
      

      【讨论】:

      • 如果我使用“command=callback”执行此操作,则无法将小部件或小部件的值(如条目中的文本)传递给函数。我将不得不使用绑定()。有没有办法以“command=callback”格式包含小部件或小部件信息?
      • @Tango lambdacommand = &lt;aCallbackHANDLER&gt;.trace_variable( &lt;aTracerTYPE&gt;, &lt;aCallbackHANDLER&gt; ) 中都作为动力马工作,仔细检查每次调用注入的 Tkinter 参数并添加您自己的 add-关于参数,您已经为 Tkinter 自动注入的那些“超越”符号占位符配备了 lambda 容器。您的附加参数应分配一个“默认”值,因此通过此技巧,您可以将所需的引用移入&lt;aWidget&gt; +PLUS+ lambda -容器可以分配另一个lambdaaVarToEVAL 然后拥有它eval( aVarToEVAL.__code__)
      猜你喜欢
      • 2023-01-04
      • 1970-01-01
      • 2021-10-08
      • 2017-01-16
      • 1970-01-01
      • 1970-01-01
      • 2020-02-19
      • 1970-01-01
      • 2019-12-20
      相关资源
      最近更新 更多