Tkinter 的奇怪函数行为 [英] Odd function behaviour with Tkinter

查看:29
本文介绍了Tkinter 的奇怪函数行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们在事件回调中有一些代码,如下所示:

<代码>...self.position.side = -self.position.sideself.update_with_board() # 以图形方式显示 self.positionai_move = self.brain.get_move(self.position)...

update() 被立即调用,但不会影响 GUI,直到 ai_move 行.

但是,当我这样做时:

<代码>...self.position.side = -self.position.sideself.update_with_board() # 以图形方式显示 self.position原始输入()ai_move = self.brain.get_move(self.position)...

它在要求输入时立即以图形方式更新.我不知道该怎么做:也许是时髦的懒惰评估或我不知道的 tkinter 调度?如何让 GUI 按照指定的顺序而不是延迟更新?

抱歉,我没有使用内置的 update() 方法,而是使用我定义的绘制方法.我将其重命名为 update_with_board(),并看到相同的行为.

def update_with_board(self):对于范围内的 i (8):对于范围内的 j(8):颜色 = "灰色" if (i+j) % 2 else "white"self.canvas.create_rectangle(self.square * i, self.square * j, self.square * (i+1), self.square * (j+1), fill=color)如果 self.position.board[8 * j + i] 在 self.ims.keys() 中:self.canvas.create_image(self.square * i + self.square/2,self.square * j + self.square/2, image = self.ims[self.position.board[8 * j + i]])

解决方案

做了更多的研究,我很确定我的评论是正确的.

UI 每次通过事件循环都会根据需要更新所有小部件.调用 update 基本上只是强制它现在运行事件循环.因此,如果您正处于事件回调过程中,则会递归地进入事件循环,这是一件非常糟糕的事情,并且可能导致无限递归.

正如 TkInter Book 所说,更新:

<块引用>

处理所有挂起的事件,调用事件回调,完成任何挂起的几何管理,根据需要重绘小部件,并调用所有挂起的空闲任务.这个方法应该小心使用,因为如果从错误的地方调用它可能会导致非常讨厌的竞争条件(例如,从事件回调中,或者从可以以任何方式从事件回调中调用的函数等).)如有疑问,请改用 update_idletasks.

同样,TkInter 参考 说:><块引用>

此方法强制更新显示.只有当您知道自己在做什么时才应该使用它,因为它可能导致不可预测的行为或循环.永远不应该从事件回调或从事件回调调用的函数中调用它.

测试一下,至少在某些情况下,当您从事件循环内部调用 update 时,TkInter 似乎只是忽略了您.大概是为了保护您免受无限递归的影响,但我还没有查看代码来验证这一点.

无论如何,这不是您想要的功能,所以它为什么不做它没有记录的事情并不重要.

如果您需要从事件回调中触发更新,请调用 update_idletasks.这实际上调用了所有挂起的空闲"任务,包括重绘,而不调用任何事件回调.(不是下次我们进入事件循环时更新并且无事可做".)

同时,在事件回调中调用 raw_input 甚至更糟.您正在阻止终端输入的主事件循环.除非这只是您出于调试目的而做的事情,否则这是一个非常糟糕的主意.即使出于调试目的,测试也是一件非常奇怪的事情,而且完全有可能发生的任何事情都与正常行为无关.

有关更多背景信息,请参阅问题TkInter:小部件如何更新,或在此站点上搜索 update_idletasks,或查看右侧的一些相关链接(其中至少有两个相关).

根据您的编辑,事实证明,您似乎遇到了相反的问题——您只是在没有告诉 TkInter 做任何事情的情况下进行更改,因此更改不会t 直到下一次通过事件循环才会出现(这意味着直到你从这个函数返回之后才会出现).但答案基本上是一样的——一种方法是用 update_idletasks 替换你对 update 的调用",另一种方法是添加对 update_idletasks 的调用"代码>".

We have some code in an event callback that looks like:

...
self.position.side = -self.position.side
self.update_with_board() # displays self.position graphically
ai_move = self.brain.get_move(self.position)
...

update() is called immediately but does not affect the GUI until the ai_move line.

However, when I do:

...
self.position.side = -self.position.side
self.update_with_board() # displays self.position graphically
raw_input()
ai_move = self.brain.get_move(self.position)
...

it updates graphically immediately as it asks for the input. I don't know what to make of this: maybe funky lazy evaluation or a tkinter scheduling thing I don't know about? How do I get the GUI to update in the order specified instead of delayed?

EDIT: Sorry, I wasn't using the built-in update() method, but rather one I defined to draw. I renamed it update_with_board(), and see the same behavior.

def update_with_board(self):
    for i in range(8):
        for j in range(8):
            color = "gray" if (i+j) % 2 else "white"
            self.canvas.create_rectangle(self.square * i, self.square * j, self.square * (i+1), self.square * (j+1), fill=color)
            if self.position.board[8 * j + i] in self.ims.keys():
                self.canvas.create_image(self.square * i + self.square/2,
                self.square * j + self.square/2, image = self.ims[self.position.board[8 * j + i]])  

解决方案

Doing a bit more research, I'm pretty sure my comment is correct.

The UI updates all widgets as needed every time through the event loop. Calling update basically just forces it to run the event loop now. So, if you're in the middle of an event callback, you're recursively entering the event loop, which is a very bad thing, and can lead to infinite recursion.

As The TkInter Book says, update:

Processes all pending events, calls event callbacks, completes any pending geometry management, redraws widgets as necessary, and calls all pending idle tasks. This method should be used with care, since it may lead to really nasty race conditions if called from the wrong place (from within an event callback, for example, or from a function that can in any way be called from an event callback, etc.). When in doubt, use update_idletasks instead.

Similarly, the TkInter reference says:

This method forces the updating of the display. It should be used only if you know what you're doing, since it can lead to unpredictable behavior or looping. It should never be called from an event callback or a function that is called from an event callback.

Testing things out, it seems like at least in some cases, TkInter just ignores you when you call update from inside the event loop. Presumably to protect you from infinite recursion, but I haven't looked at the code to verify that.

At any rate, this isn't the function you want, so it doesn't really matter why exactly it isn't doing what it wasn't documented to do.

If you need to trigger an update from within an event callback, call update_idletasks. This actually calls all pending "idle" tasks, including redraws, without calling any event callbacks. (Not "update next time we're in the event loop and have nothing else to do".)

Meanwhile, calling raw_input inside an event callback is even worse. You're blocking the main event loop on terminal input. Unless this was just something you did for debugging purposes, it's a very bad idea. And even for debugging purposes, it's a very odd thing to test, and it's entirely plausible that whatever happens will have no relevance to normal behavior.

For more background, see the question TkInter: How do widgets update, or search update_idletasks on this site, or look at some of the Related links on the right side (at least two of which are relevant).

Based on your edit, as it turns out, it sounds like you had kind of the opposite problem—you were just changing things without telling TkInter to do anything about it, so the changes wouldn't show up until the next time through the event loop (which means not until after you return from this function). But the answer is basically the same—one way it's "replace your call to update with update_idletasks", the other way it's "add a call to update_idletasks".

这篇关于Tkinter 的奇怪函数行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆