Python Tkinter理解主循环
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29158220/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Tkinter understanding mainloop
提问by midkin
Till now, I used to end my Tkiter programs with: tk.mainloop()
, or nothing would show up! See example:
直到现在,我过去常常用: 结束我的 Tkiter 程序tk.mainloop()
,否则什么都不会出现!见示例:
from Tkinter import *
import random
import time
tk = Tk()
tk.title = "Game"
tk.resizable(0,0)
tk.wm_attributes("-topmost", 1)
canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()
class Ball:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
def draw(self):
pass
ball = Ball(canvas, "red")
tk.mainloop()
However, when tried the next step in this program(making the ball move by time), the book am reading from, says to do the following. Change the draw function to:
但是,当尝试该程序的下一步(使球按时间移动)时,正在阅读的书说要执行以下操作。将绘制函数更改为:
def draw(self):
self.canvas.move(self.id, 0, -1)
and add the following code to my program:
并将以下代码添加到我的程序中:
while 1:
ball.draw()
tk.update_idletasks()
tk.update()
time.sleep(0.01)
But I noticed that adding this block of code, made the use of tk.mainloop()
useless, since everything would show up even without it!!!
但是我注意到添加这个代码块,使使用变得tk.mainloop()
无用,因为即使没有它,一切都会显示出来!!!
At this moment I should mention that my book never talks about tk.mainloop()
(maybe because it uses Python 3) but I learned about it searching the web since my programs didn't work by copying book's code!
此刻我应该提到我的书从来没有提到过tk.mainloop()
(也许是因为它使用 Python 3),但我通过搜索网络了解到它,因为我的程序无法通过复制书中的代码运行!
So I tried doing the following that would not work!!!
所以我尝试做以下行不通的事情!!!
while 1:
ball.draw()
tk.mainloop()
time.sleep(0.01)
What's going on? What does tk.mainloop()
? What does tk.update_idletasks()
and tk.update()
do and how that differs from tk.mainloop()
? Should I use the above loop?tk.mainloop()
? or both in my programs?
这是怎么回事?有什么作用tk.mainloop()
?做什么tk.update_idletasks()
和tk.update()
做什么以及它与tk.mainloop()
什么不同?我应该使用上面的循环吗?tk.mainloop()
? 或者两者都在我的程序中?
采纳答案by 7stud
tk.mainloop()
blocks. What that means is that execution of your pythonprogram halts there. You can see that by writing:
tk.mainloop()
块。这意味着你的python程序的执行在那里停止。你可以通过写来看到:
while 1:
ball.draw()
tk.mainloop()
print "hello" #NEW CODE
time.sleep(0.01)
You will never see the output from the print statement. Because there is no loop, the ball doesn't move.
您永远不会看到打印语句的输出。因为没有循环,球不会移动。
On the other hand, the methods update_idletasks()
and update()
here:
另一方面,方法update_idletasks()
和update()
这里:
while True:
ball.draw()
tk.update_idletasks()
tk.update()
...do not block; execution continues on after those methods finish, so the while loop executes over and over, which makes the ball move.
...不要阻塞;在这些方法完成后继续执行,因此 while 循环会一遍又一遍地执行,从而使球移动。
An infinite loop containing the method calls update_idletasks()
and update()
can act as a substitute for calling tk.mainloop()
. Note that the whole while loop can be said to blockjust like tk.mainloop()
because nothing after the while loop will execute.
一个包含方法调用的无限循环update_idletasks()
,update()
可以作为调用的替代tk.mainloop()
。请注意,整个 while 循环可以说是阻塞的,tk.mainloop()
因为 while 循环之后的任何内容都不会执行。
However, tk.mainloop()
is not a substitute for just the lines:
但是,tk.mainloop()
不能代替以下行:
tk.update_idletasks()
tk.update()
Rather, tk.mainloop()
is a substitute for the whole while loop:
相反,它tk.mainloop()
是整个 while 循环的替代品:
while True:
tk.update_idletasks()
tk.update()
Response to comment:
回复评论:
Here is what the tcl docssay:
这是tcl 文档所说的:
update idletasks
This subcommand of update flushes all currently-scheduled idle events from Tcl's event queue. Idle events are used to postpone processing until “there is nothing else to do”, with the typical use case for them being Tk's redrawing and geometry recalculations. By postponing these until Tk is idle, expensive redraw operations are not done until everything from a cluster of events (e.g., button release, change of current window, etc.) are processed at the script level. This makes Tk seem much faster, but if you're in the middle of doing some long running processing, it can also mean that no idle events are processed for a long time. By calling update idletasks, redraws due to internal changes of state are processed immediately. (Redraws due to system events, e.g., being deiconified by the user, need a full update to be processed.)
APN As described in Update considered harmful, use of update to handle redraws not handled by update idletasks has many issues. Joe English in a comp.lang.tcl posting describes an alternative:
更新空闲任务
update 的这个子命令从 Tcl 的事件队列中刷新所有当前调度的空闲事件。空闲事件用于推迟处理直到“没有其他事情可做”,它们的典型用例是 Tk 的重绘和几何重新计算。通过将这些推迟到 Tk 空闲,昂贵的重绘操作不会在脚本级别处理来自一组事件(例如,按钮释放、当前窗口的更改等)的所有内容之前完成。这使得 Tk 看起来更快,但如果您正在执行一些长时间运行的处理,这也可能意味着很长时间没有处理空闲事件。通过调用 update idletasks,立即处理由于内部状态更改而导致的重绘。(由于系统事件重绘,例如被用户去图标化,
APN 正如更新被认为是有害的,使用更新来处理不是由更新空闲任务处理的重绘有很多问题。Joe English 在 comp.lang.tcl 帖子中描述了另一种选择:
So update_idletasks()
causes some subset of events to be processed that update()
causes to be processed.
因此update_idletasks()
会导致某些事件子集被处理,从而update()
导致被处理。
From the update docs:
从更新文档:
update ?idletasks?
The update command is used to bring the application “up to date” by entering the Tcl event loop repeatedly until all pending events (including idle callbacks) have been processed.
If the idletasks keyword is specified as an argument to the command, then no new events or errors are processed; only idle callbacks are invoked. This causes operations that are normally deferred, such as display updates and window layout calculations, to be performed immediately.
KBK (12 February 2000) -- My personal opinion is that the [update] command is not one of the best practices, and a programmer is well advised to avoid it. I have seldom if ever seen a use of [update] that could not be more effectively programmed by another means, generally appropriate use of event callbacks. By the way, this caution applies to all the Tcl commands (vwait and tkwait are the other common culprits) that enter the event loop recursively, with the exception of using a single [vwait] at global level to launch the event loop inside a shell that doesn't launch it automatically.
The commonest purposes for which I've seen [update] recommended are: 1) Keeping the GUI alive while some long-running calculation is executing. See Countdown program for an alternative. 2) Waiting for a window to be configured before doing things like geometry management on it. The alternative is to bind on events such as that notify the process of a window's geometry. See Centering a window for an alternative.
What's wrong with update? There are several answers. First, it tends to complicate the code of the surrounding GUI. If you work the exercises in the Countdown program, you'll get a feel for how much easier it can be when each event is processed on its own callback. Second, it's a source of insidious bugs. The general problem is that executing [update] has nearly unconstrained side effects; on return from [update], a script can easily discover that the rug has been pulled out from under it. There's further discussion of this phenomenon over at Update considered harmful.
更新?空闲任务?
更新命令用于通过重复进入 Tcl 事件循环直到所有未决事件(包括空闲回调)都被处理来使应用程序“更新”。
如果将 idletasks 关键字指定为命令的参数,则不会处理新的事件或错误;仅调用空闲回调。这会导致立即执行通常延迟的操作,例如显示更新和窗口布局计算。
KBK(2000 年 2 月 12 日)——我个人认为 [update] 命令不是最佳实践之一,建议程序员避免使用它。我很少看到使用 [update] 不能通过另一种方式更有效地编程,通常适当使用事件回调。顺便说一句,这种警告适用于递归进入事件循环的所有 Tcl 命令(vwait 和 tkwait 是其他常见的罪魁祸首),除了在全局级别使用单个 [vwait] 在 shell 内启动事件循环这不会自动启动它。
我所看到的 [update] 推荐的最常见目的是: 1) 在执行一些长时间运行的计算时保持 GUI 处于活动状态。有关替代方案,请参阅倒计时程序。2)在对窗口进行几何管理之类的操作之前等待配置窗口。另一种方法是绑定事件,例如通知窗口几何图形的过程。请参阅将窗口居中以获取替代方法。
更新有什么问题?有几个答案。首先,它往往会使周围 GUI 的代码复杂化。如果您在 Countdown 程序中进行练习,您将感受到当每个事件都在其自己的回调中处理时会变得多么容易。其次,它是潜在错误的来源。普遍的问题是执行 [update] 有几乎不受约束的副作用;从 [update] 返回时,脚本可以很容易地发现地毯已从其下方拉出。更新中对这种现象的进一步讨论被认为是有害的。
.....
.....
Is there any chance I can make my program work without the while loop?
有没有可能让我的程序在没有 while 循环的情况下工作?
Yes, but things get a little tricky. You might think something like the following would work:
是的,但事情变得有点棘手。您可能认为以下内容会起作用:
class Ball:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
def draw(self):
while True:
self.canvas.move(self.id, 0, -1)
ball = Ball(canvas, "red")
ball.draw()
tk.mainloop()
The problem is that ball.draw() will cause execution to enter an infinite loop in the draw() method, so tk.mainloop() will never execute, and your widgets will never display. In gui programming, infinite loops have to be avoided at all costs in order to keep the widgets responsive to user input, e.g. mouse clicks.
问题是 ball.draw() 会导致执行在 draw() 方法中进入无限循环,因此 tk.mainloop() 永远不会执行,并且您的小部件永远不会显示。在 gui 编程中,必须不惜一切代价避免无限循环,以保持小部件对用户输入(例如鼠标点击)的响应。
So, the question is: how do you execute something over and over again without actually creating an infinite loop? Tkinter has an answer for that problem: a widget's after()
method:
所以,问题是:如何在不实际创建无限循环的情况下一遍又一遍地执行某事?Tkinter 对这个问题有一个答案:一个小部件的after()
方法:
from Tkinter import *
import random
import time
tk = Tk()
tk.title = "Game"
tk.resizable(0,0)
tk.wm_attributes("-topmost", 1)
canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()
class Ball:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
def draw(self):
self.canvas.move(self.id, 0, -1)
self.canvas.after(1, self.draw) #(time_delay, method_to_execute)
ball = Ball(canvas, "red")
ball.draw() #Changed per Bryan Oakley's comment
tk.mainloop()
The after() method doesn't block(it actually creates another thread of execution), so execution continues on in your python program after after() is called, which means tk.mainloop() executes next, so your widgets get configured and displayed. The after() method also allows your widgets to remain responsive to other user input. Try running the following program, and then click your mouse on different spots on the canvas:
after() 方法不会阻塞(它实际上创建了另一个执行线程),因此在调用 after() 之后在您的 Python 程序中继续执行,这意味着 tk.mainloop() 接下来执行,因此您的小部件得到配置和显示。after() 方法还允许您的小部件保持对其他用户输入的响应。尝试运行以下程序,然后在画布上的不同位置单击鼠标:
from Tkinter import *
import random
import time
root = Tk()
root.title = "Game"
root.resizable(0,0)
root.wm_attributes("-topmost", 1)
canvas = Canvas(root, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()
class Ball:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
self.canvas.bind("<Button-1>", self.canvas_onclick)
self.text_id = self.canvas.create_text(300, 200, anchor='se')
self.canvas.itemconfig(self.text_id, text='hello')
def canvas_onclick(self, event):
self.canvas.itemconfig(
self.text_id,
text="You clicked at ({}, {})".format(event.x, event.y)
)
def draw(self):
self.canvas.move(self.id, 0, -1)
self.canvas.after(50, self.draw)
ball = Ball(canvas, "red")
ball.draw() #Changed per Bryan Oakley's comment.
root.mainloop()
回答by Bryan Oakley
while 1:
root.update()
... is (very!) roughlysimilar to:
...是(非常!)大致类似于:
root.mainloop()
The difference is, mainloop
is the correct way to code and the infinite loop is subtly incorrect. I suspect, though, that the vast majority of the time, either will work. It's just that mainloop
is a much cleaner solution. After all, calling mainloop
is essentially this under the covers:
不同之处在于,mainloop
是正确的编码方式,而无限循环则微妙地不正确。不过,我怀疑在绝大多数情况下,两者都可以奏效。这只是mainloop
一个更清洁的解决方案。毕竟,调用mainloop
本质上是这样的:
while the_window_has_not_been_destroyed():
wait_until_the_event_queue_is_not_empty()
event = event_queue.pop()
event.handle()
... which, as you can see, isn't much different than your own while loop. So, why create your own infinite loop when tkinter already has one you can use?
...正如您所看到的,这与您自己的 while 循环没有太大不同。那么,当 tkinter 已经有了一个可以使用的无限循环时,为什么还要创建自己的无限循环呢?
Put in the simplest terms possible: always call mainloop
as the last logical line of code in your program. That's how Tkinter was designed to be used.
尽可能用最简单的术语:始终mainloop
作为程序中最后一个逻辑代码行调用。这就是 Tkinter 的设计用途。
回答by BuvinJ
I'm using an MVC / MVA design pattern, with multiple types of "views". One type is a "GuiView", which is a Tk window. I pass a view reference to my window object which does things like link buttons back to view functions (which the adapter / controller class also calls).
我正在使用 MVC/MVA 设计模式,具有多种类型的“视图”。一种类型是“GuiView”,它是一个 Tk 窗口。我将视图引用传递给我的 window 对象,该对象执行诸如将链接按钮返回到视图函数(适配器/控制器类也调用)之类的操作。
In order to do that, the view object constructor needed to be completed prior to creating the window object. After creating and displaying the window, I wanted to do some initial tasks with the view automatically. At first I tried doing them post mainloop(), but that didn't work because mainloop() blocked!
为了做到这一点,需要在创建窗口对象之前完成视图对象构造函数。创建并显示窗口后,我想自动对视图执行一些初始任务。起初我尝试在 mainloop() 之后做它们,但那没有用,因为 mainloop() 被阻塞了!
As such, I created the window object and used tk.update() to draw it. Then, I kicked off my initial tasks, and finally started the mainloop.
因此,我创建了 window 对象并使用 tk.update() 来绘制它。然后,我开始了我最初的任务,最后开始了主循环。
import Tkinter as tk
class Window(tk.Frame):
def __init__(self, master=None, view=None ):
tk.Frame.__init__( self, master )
self.view_ = view
""" Setup window linking it to the view... """
class GuiView( MyViewSuperClass ):
def open( self ):
self.tkRoot_ = tk.Tk()
self.window_ = Window( master=None, view=self )
self.window_.pack()
self.refresh()
self.onOpen()
self.tkRoot_.mainloop()
def onOpen( self ):
""" Do some initial tasks... """
def refresh( self ):
self.tkRoot_.update()