Tkinter理解主循环 [英] Tkinter understanding mainloop

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

问题描述

直到现在,我过去常常用:tk.mainloop() 结束我的 Tkinter 程序,否则什么都不会显示!见示例:

Till now, I used to end my Tkinter programs with: tk.mainloop(), or nothing would show up! See example:

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()

但是,当尝试该程序的下一步(使球按时间移动)时,正在阅读的书说要执行以下操作.所以我把draw函数改成:

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. So I changed 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)

但我注意到添加这段代码后,tk.mainloop() 的使用变得毫无用处,因为即使没有它,一切都会显示出来!!!

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()(可能是因为它使用 Python 3)但我在网上搜索时了解到它,因为我的程序没有不能通过复制书的代码来工作!

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!

所以我尝试了以下不起作用的方法!!!

So I tried doing the following that would not work!!!

while 1:
    ball.draw()
    tk.mainloop()
    time.sleep(0.01)

这是怎么回事?tk.mainloop() 是什么?tk.update_idletasks()tk.update() 有什么作用?它们与 tk.mainloop() 有何不同?我应该使用上面的循环吗?tk.mainloop()?还是两者都在我的程序中?

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() blocks.这意味着您的 Python 命令的执行在那里停止.你可以通过写来看到:

tk.mainloop() blocks. It means that execution of your Python commands halts there. You can see that by writing:

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.

另一方面,方法 update_idletasks()update() 在这里:

On the other hand, the methods update_idletasks() and update() here:

while True:
    ball.draw()
    tk.update_idletasks()
    tk.update()

...不要阻塞;这些方法完成后,将继续执行,因此 while 循环将一遍又一遍地执行,从而使球移动.

...do not block; after those methods finish, execution will continue, so the while loop will execute over and over, which makes the ball move.

包含方法调用 update_idletasks()update() 的无限循环可以替代调用 tk.mainloop().请注意,整个 while 循环可以说是 block 就像 tk.mainloop() 一样,因为 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 block just like tk.mainloop() because nothing after the while loop will execute.

然而,tk.mainloop() 并不能代替这些行:

However, tk.mainloop() is not a substitute for just the lines:

tk.update_idletasks()
tk.update()

相反,tk.mainloop() 是整个 while 循环的替代:

Rather, tk.mainloop() is a substitute for the whole while loop:

while True:
    tk.update_idletasks()
    tk.update()

回复评论:

以下是 tcl 文档 所说的:

更新空闲任务

这个更新子命令刷新所有当前调度的空闲事件来自 Tcl 的事件队列.空闲事件用于推迟处理直到无事可做",典型用例为它们是 Tk 的重绘和几何重新计算.通过推迟这些直到 Tk 空闲,昂贵的重绘操作直到来自一组事件的所有内容(例如,按钮释放、更改当前窗口等)在脚本级别处理.这使得 Tk看起来快得多,但如果你正在做一些很长的事情运行处理,这也可能意味着没有处理空闲事件许久.通过调用更新空闲任务,由于内部重绘立即处理状态更改.(由于系统重绘事件,例如,被用户去图标化,需要一个完整的更新处理.)

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 如更新中所述,认为有害,使用更新来处理未由更新空闲任务处理的重绘有很多问题.乔英语在 comp.lang.tcl 帖子中描述了另一种选择:

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_idletasks() 会导致处理某些 update() 导致处理的事件子集.

So update_idletasks() causes some subset of events to be processed that update() causes to be processed.

来自更新文档:

更新?空闲任务?

更新命令用于通过以下方式使应用程序更新"重复进入 Tcl 事件循环,直到所有未决事件(包括空闲回调)已处理.

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.

如果将 idletasks 关键字指定为命令的参数,然后不会处理新的事件或错误;只有空闲回调是调用.这会导致通常被推迟的操作,例如要执行的显示更新和窗口布局计算立即.

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(2000 年 2 月 12 日)--我个人认为 [更新]命令不是最佳实践之一,程序员很好建议避免它.我很少看到[更新]的使用无法通过其他方式更有效地编程,通常适当使用事件回调.顺便说一句,这个警告适用到所有 Tcl 命令(vwait 和 tkwait 是其他常见的罪魁祸首)递归进入事件循环,除了在全局级别使用单个 [vwait] 在内部启动事件循环一个不会自动启动的 shell.

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. 在一些长时间运行的计算过程中保持 GUI 处于活动状态执行.有关替代方案,请参阅倒计时程序.2)在做类似的事情之前等待一个窗口被配置几何管理就可以了.另一种方法是绑定事件,例如作为通知窗口几何图形的过程.看将窗口居中以获取替代方案.

更新有什么问题?有几个答案.首先,它倾向于使周围 GUI 的代码复杂化.如果你工作倒计时程序中的练习,你会感觉到有多少当每个事件在其自己的回调中处理时会更容易.其次,它是潜在错误的来源.一般的问题是执行 [update] 具有几乎不受约束的副作用;返回时从 [更新],脚本可以很容易地发现地毯已被从它下面拉出来.对此有进一步讨论更新中的现象被认为是有害的.

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.

.....

有没有可能让我的程序在没有 while 循环的情况下工作?

Is there any chance I can make my program work without the while loop?

是的,但事情变得有点棘手.您可能认为以下内容会起作用:

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()

问题是 ball.draw() 会导致执行在 draw() 方法中进入无限循环,因此 tk.mainloop() 永远不会执行,并且您的小部件永远不会显示.在 gui 编程中,必须不惜一切代价避免无限循环,以保持小部件对用户输入的响应,例如鼠标点击.

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.

那么,问题是:如何在不实际创建无限循环的情况下一遍又一遍地执行某事?Tkinter 对这个问题有一个答案:小部件的 after() 方法:

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:

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()

after() 方法不会阻塞(它实际上创建了另一个执行线程),因此在调用 after() 后,python 程序中的执行会继续进行,这意味着 tk.mainloop() 接下来执行,以便您的小部件得到配置和显示.after() 方法还允许您的小部件保持对其他用户输入的响应.尝试运行以下程序,然后在画布上的不同位置单击鼠标:

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:

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()

这篇关于Tkinter理解主循环的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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