直到现在,我曾经用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()
然而,当尝试了这个程序的下一步(让球随着时间移动)时,这本书正在阅读,说要做到以下几点。将绘图功能更改为:
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),但我学会了它在网上搜索,因为我的程序无法复制书籍代码!
所以我尝试了以下无效的工作!!!
while 1:
ball.draw()
tk.mainloop()
time.sleep(0.01)
发生了什么事? tk.mainloop()
是什么? tk.update_idletasks()
和tk.update()
做了什么以及它与tk.mainloop()
的区别?我应该使用上面的循环吗?tk.mainloop()
?或两者都在我的程序中?
答案 0 :(得分:60)
tk.mainloop()
阻止。这意味着你的 python 程序的执行停止了。你可以通过写:
while 1:
ball.draw()
tk.mainloop()
print "hello" #NEW CODE
time.sleep(0.01)
您永远不会看到print语句的输出。因为没有循环,所以球不会移动。
另一方面,这里的方法update_idletasks()
和update()
:
while True:
ball.draw()
tk.update_idletasks()
tk.update()
......不要阻止;在这些方法完成后继续执行,因此while循环一遍又一遍地执行,这使得球移动。
包含方法调用update_idletasks()
和update()
的无限循环可以替代调用tk.mainloop()
。请注意,整个while循环可以说是 block ,就像tk.mainloop()
一样,因为while循环之后不会执行任何操作。
但是,tk.mainloop()
不能代替这些行:
tk.update_idletasks()
tk.update()
相反,tk.mainloop()
是整个while循环的替代品:
while True:
tk.update_idletasks()
tk.update()
对评论的回应:
以下是tcl docs所说的内容:
更新idletasks
此更新子命令刷新所有当前计划的空闲事件 来自Tcl的事件队列。空闲事件用于推迟处理 直到“没有别的事可做”,具有典型的用例 他们是Tk的重绘和几何重新计算。通过推迟 这些直到Tk闲置,昂贵的重绘操作直到完成 来自一系列事件的所有事物(例如,按钮释放,改变 当前窗口等)在脚本级别处理。这使得Tk 看起来要快得多,但如果你正在做一些漫长的事情 运行处理,也可能意味着没有处理空闲事件 需很长时间。通过调用更新idletasks,由于内部重绘 立即处理状态的变化。 (由于系统重绘 例如,由用户取消图标化的事件需要完全更新 处理。)
APN如更新中所述有害,使用更新来处理 更新idletasks未处理的重绘有很多问题。乔英语 在comp.lang.tcl帖子中描述了另一种选择:
因此,update_idletasks()
会导致处理update()
导致处理的事件子集。
来自update docs:
更新?idletasks?
更新命令用于使应用程序“更新” 重复进入Tcl事件循环,直到所有挂起事件 (包括空闲回调)已经处理完毕。
如果将idletasks关键字指定为命令的参数, 然后没有处理新的事件或错误;只有空闲的回调 调用。这会导致通常延迟的操作,例如 要执行的显示更新和窗口布局计算 立即
KBK(2000年2月12日) - 我的个人意见是[更新] 命令不是最佳实践之一,程序员也很好 建议避免它。我很少见过使用[更新]那个 通常,用其他方法无法更有效地编程 适当使用事件回调。顺便说一句,这种谨慎适用 所有Tcl命令(vwait和tkwait是另一个常见命令) culprits)以递归方式进入事件循环,但 在全局级别使用单个[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()
问题是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()
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()
答案 1 :(得分:11)
while 1:
root.update()
...(非常!)大致类似于:
root.mainloop()
不同之处在于,mainloop
是正确的编码方式,无限循环是微妙的错误。但我怀疑,绝大多数时候,要么会奏效。只是mainloop
是一个更清洁的解决方案。毕竟,调用mainloop
基本上就是这个问题:
while the_window_has_not_been_destroyed():
wait_until_the_event_queue_is_not_empty()
event = event_queue.pop()
event.handle()
...正如您所看到的,与您自己的while循环没有多大区别。那么,为什么在tkinter已经有一个你可以使用的循环时创建自己的无限循环?
尽可能简单地说明:始终将mainloop
称为程序中最后一行逻辑代码。这就是Tkinter的设计用途。
答案 2 :(得分:1)
我使用MVC / MVA设计模式,有多种类型的&#34;视图&#34;。一种类型是&#34; GuiView&#34;,这是一个Tk窗口。我将视图引用传递给我的window对象,它将链接按钮返回到视图函数(适配器/控制器类也调用)。
为了做到这一点,需要在创建窗口对象之前完成视图对象构造函数。创建并显示窗口后,我想自动对视图执行一些初始任务。起初我尝试将它们发布到mainloop()之后,但由于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()