Tkinter了解mainloop

mid*_*kin 49 python tkinter

直到现在,我曾经用以下结束我的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()
Run Code Online (Sandbox Code Playgroud)

然而,当尝试下这个程序的下一步(让球随时间移动)时,本书正在阅读,说要做到以下几点.将绘图功能更改为:

def draw(self):
    self.canvas.move(self.id, 0, -1)
Run Code Online (Sandbox Code Playgroud)

并将以下代码添加到我的程序:

while 1:
    ball.draw()
    tk.update_idletasks()
    tk.update()
    time.sleep(0.01)
Run Code Online (Sandbox Code Playgroud)

但我注意到添加这段代码,使用tk.mainloop()无用,因为即使没有它,一切都会出现!

在这一刻,我应该提到我的书从未谈到tk.mainloop()(也许是因为它使用Python 3)但我学会了它在网上搜索,因为我的程序不能通过复制书的代码工作!

所以我尝试做以下不起作用!

while 1:
    ball.draw()
    tk.mainloop()
    time.sleep(0.01)
Run Code Online (Sandbox Code Playgroud)

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

7st*_*tud 73

tk.mainloop() .这意味着你的python程序的执行停止了.你可以通过写:

while 1:
    ball.draw()
    tk.mainloop()
    print "hello"   #NEW CODE
    time.sleep(0.01)
Run Code Online (Sandbox Code Playgroud)

您永远不会看到print语句的输出.因为没有环,所以球不会移动.

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

while True:
    ball.draw()
    tk.update_idletasks()
    tk.update()
Run Code Online (Sandbox Code Playgroud)

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

包含方法调用的无限循环update_idletasks(),update()可以作为调用的替代tk.mainloop().请注意,整个while循环可以说就像阻塞一样,tk.mainloop()因为while循环之后没有任何东西会执行.

但是,tk.mainloop()不能仅仅替代线条:

tk.update_idletasks()
tk.update()
Run Code Online (Sandbox Code Playgroud)

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

while True:
    tk.update_idletasks()
    tk.update()
Run Code Online (Sandbox Code Playgroud)

回复评论:

以下是tcl文档所说的内容:

更新idletasks

此更新子命令从Tcl的事件队列中刷新所有当前调度的空闲事件.空闲事件用于推迟处理,直到"没有别的事情可做",其中典型的用例是Tk的重绘和几何重新计算.通过推迟这些直到Tk空闲,直到在脚本级别处理来自一组事件(例如,按钮释放,当前窗口的改变等)的所有内容之后才进行昂贵的重绘操作.这使得Tk看起来更快,但是如果你正在进行一些长时间运行的处理,那么它也意味着很长时间没有处理空闲事件.通过调用更新idletasks,可以立即处理由于状态内部更改而导致的重绘.(由于系统事件而重绘,例如,被用户取消图标化,需要完整更新才能处理.)

APN如更新中所述有害,使用更新来处理未由更新idletasks处理的重绘有许多问题.comp.lang.tcl帖子中的Joe English描述了另一种选择:

因此update_idletasks()导致处理update()导致处理的事件的一些子集.

更新文档:

更新?idletasks?

update命令用于通过重复进入Tcl事件循环使应用程序"更新",直到处理完所有挂起事件(包括空闲回调).

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

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

我看到[更新]推荐的最常见目的是:1)在执行一些长时间运行的计算时保持GUI处于活动状态.请参阅倒计时程序以获取替代方案.2)在对其进行几何管理之前等待配置窗口.另一种方法是绑定事件,例如通知窗口几何体的过程.请参阅居中窗口以获取替代方案.

更新有什么问题?有几个答案.首先,它往往使周围GUI的代码复杂化.如果你在倒计时程序中练习练习,你会发现每个事件在自己的回调上处理时会有多容易.其次,它是阴险错误的根源.一般问题是执行[更新]几乎没有受到限制的副作用; 从[更新]返回时,脚本可以很容易地发现地毯已经从它下面拉出来了.在被认为有害的更新中,有关此现象的进一步讨论.

.....

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

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

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()
Run Code Online (Sandbox Code Playgroud)

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

所以,问题是:如何在不实际创建无限循环的情况下一遍又一遍地执行某些操作?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()
Run Code Online (Sandbox Code Playgroud)

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()
Run Code Online (Sandbox Code Playgroud)

  • 这个语句不正确:_"after()方法没有阻塞(它实际上创建了另一个执行线程)"_ - `after`确实_not_创建另一个执行线程.Tkinter是单线程的.`after`只是将一个函数添加到队列中. (5认同)
  • 在程序的最后,用`canvas.after(0,ball.draw)`开始动画.你不需要在这里使用`after` - 你可以直接调用`ball.draw`来解决问题. (4认同)
  • 从来没有理由同时调用`update_idletasks`和`update`.`update`执行`update_idletasks`所做的一切,等等. (3认同)

Bry*_*ley 13

while 1:
    root.update()
Run Code Online (Sandbox Code Playgroud)

......(非常!)大致类似于:

root.mainloop()
Run Code Online (Sandbox Code Playgroud)

不同的是,mainloop正确的编码方式和无限循环是巧妙的错误.但我怀疑,绝大多数时候,要么会奏效.这只是mainloop一个更清洁的解决方案.毕竟,呼叫mainloop基本上是这样的:

while the_window_has_not_been_destroyed():
    wait_until_the_event_queue_is_not_empty()
    event = event_queue.pop()
    event.handle()
Run Code Online (Sandbox Code Playgroud)

......正如你所看到的,与你自己的while循环没什么不同.那么,为什么在tkinter已经有一个你可以使用的循环时创建自己的无限循环?

尽可能简单地说:始终将程序mainloop中的最后一行代码称为.这就是Tkinter的设计用途.

  • 在我的例子中,什么是(对你来说)让我的球在不使用新循环的情况下移动但继续使用主循环的正确方法? (3认同)