Tkinter理解mainloop
直到现在,我用tk.mainloop()
来结束我的Tkiter程序,否则什么都不会显示! 看例子:
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()
然而,当试图在这个程序中的下一步(使球运动时间),这本书正在阅读,说要做到以下几点。 将绘图function更改为:
def draw(self): self.canvas.move(self.id, 0, -1)
并将下面的代码添加到我的程序中:
while 1: ball.draw() tk.update_idletasks() tk.update() time.sleep(0.01)
但我注意到,添加这块代码,使用tk.mainloop()
没用,因为即使没有它,一切都会显示!
在这一刻我应该提到,我的书从来没有谈到tk.mainloop()
(也许是因为它使用Python 3),但我了解了它在网上search,因为我的程序没有通过复制书的代码工作!
所以我试着做下面的,不会工作!
while 1: ball.draw() tk.mainloop() time.sleep(0.01)
这是怎么回事? tk.mainloop()
什么? 什么tk.update_idletasks()
和tk.update()
做,以及如何从tk.mainloop()
不同? 我应该使用上面的循环? tk.mainloop()
? 或者在我的程序中?
谢谢!
tk.mainloop()
块 。 这意味着你的python程序的执行停在那里。 你可以看到,通过写作:
while 1: ball.draw() tk.mainloop() print "hello" #NEW CODE time.sleep(0.01)
您将永远不会看到打印语句的输出。 因为没有圈,球不动。
另一方面,方法update_idletasks()
和update()
在这里:
while True: ball.draw() tk.update_idletasks() tk.update()
…不要阻止; 这些方法完成后继续执行,所以while循环一遍又一遍地执行,这使得球移动。
包含方法调用update_idletasks()
和update()
的无限循环可以作为调用tk.mainloop()
的替代方法。 请注意,整个while循环可以说就像tk.mainloop()
一样tk.mainloop()
因为while循环之后什么也不会执行。
但是, tk.mainloop()
不能代替这些行:
tk.update_idletasks() tk.update()
相反, tk.mainloop()
是整个while循环的替代品:
while True: tk.update_idletasks() tk.update()
回应评论:
以下是tcl文档所说的内容:
更新idletasks
这个更新子命令从Tcl的事件队列中清除所有当前计划的空闲事件。 空闲事件用于推迟处理,直到“没有别的事情可做”,典型的用例是Tk的重绘和几何重新计算。 通过推迟这些操作,直到Tk处于空闲状态,直到在脚本级别处理来自一组事件(例如,button释放,当前窗口的改变等)的所有事件,才执行昂贵的重绘操作。 这使得Tk看起来要快得多,但是如果你正在进行一些长时间运行的处理,那么也可能意味着没有空闲事件被长时间处理。 通过调用更新idletasks,由于状态的内部更改而重新绘制立即被处理。 (由于系统事件而重绘,例如由用户去除噪音,需要处理完整的更新。)
APN如更新认为有害的描述中所述,使用更新来处理未由更新idletasks处理的重绘有许多问题。 Joe English在comp.lang.tcl文章中描述了一个替代scheme:
所以update_idletasks()
会导致update()
导致处理的事件的一些子集被处理。
从更新文档 :
更新?idletasks?
更新命令用于通过重复inputTcl事件循环直到所有未决事件(包括空闲callback)都被处理,使应用程序“最新”。
如果将idletasks关键字指定为该命令的参数,则不会处理新的事件或错误; 只有空闲的callback被调用。 这会导致通常延迟的操作(例如显示更新和窗口布局计算)立即执行。
KBK(2000年2月12日) – 我个人认为[更新]命令不是最佳实践之一,程序员最好避免这种做法。 我很less见过使用[更新]的方法,这种方法不能通过其他方式更有效地编程,通常适当地使用事件callback。 顺便说一下,这个警告适用于所有的Tcl命令(vwait和tkwait是其他常见的罪魁祸首),recursion地进入事件循环,除了在全局级别使用单个[vwait]来启动shell内部的事件循环不会自动启动它。
我见过[更新]最常见的目的是:1)保持GUI活着,而一些长期运行的计算正在执行。 请参阅倒数计划以获得其他选 2)在做几何pipe理之前等待窗口被configuration。 另一种方法是绑定事件,例如通知窗口几何的过程。 请参阅将一个窗口置于另一个窗口中。
更新有什么问题? 有几个答案。 首先,它倾向于使周围GUI的代码复杂化。 如果您在倒计时程序中进行练习,您会感觉到每个事件在自己的callback中处理时会变得多容易。 其次,这是一个阴险的bug。 一般问题是执行[update]几乎没有约束的副作用; 从[更新]中返回,一个脚本可以很容易地发现地毯已经从它下面拔出。 在更新被认为是有害的。
…..
有没有机会让我的程序在没有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()
问题是ball.draw()将导致执行在draw()方法中进入一个无限循环,所以tk.mainloop()将永远不会执行,并且您的小部件将永远不会显示。 在gui编程中,必须不惜一切代价避免无限循环,以保持小部件对用户input的响应,例如鼠标点击。
所以,问题是:你如何一次又一次地执行某些事情而不实际创build一个无限循环? 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()
after()方法不会阻塞 (它实际上会创build另一个执行线程),因此在调用after()之后,在python程序中继续执行,这意味着tk.mainloop()会执行下一个,所以您的小部件将被configuration并显示。 after()方法还允许您的小部件保持对其他用户input的响应。 尝试运行以下程序,然后在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()
while 1: root.update()
…非常类似于:
root.mainloop()
不同之处在于, mainloop
循环是正确的编码方式,而无限循环则是微妙的错误。 不过,我怀疑,绝大多数时候,要么会奏效。 这只是mainloop
是一个更清洁的解决scheme。 毕竟,调用mainloop
基本上是这样的:
while the_window_has_not_been_destroyed(): wait_until_the_event_queue_is_not_empty() event = event_queue.pop() event.handle()
……如你所见,它与你自己的循环没有什么不同。 那么,为什么创build自己的无限循环时,tkinter已经有一个你可以使用?
尽可能使用最简单的术语: 始终调用mainloop
作为程序中最后一个逻辑代码行 。 这就是Tkinter的devise目的。
我正在使用MVC / MVAdevise模式,有多种types的“视图”。 一种types是“GuiView”,它是一个Tk窗口。 我传递一个视图引用到我的窗口对象,像链接button回到查看function(适配器/控制器类也调用)。
为了做到这一点,视图对象构造函数需要在创build窗口对象之前完成。 创build并显示窗口后,我想自动执行视图的一些初始任务。 起初,我试图做他们发布主循环(),但没有工作,因为mainloop()阻止!
就这样,我创build了窗口对象,并使用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()