Tkinter了解mainloop

时间:2015-03-20 02:09:18

标签: python tkinter

直到现在,我曾经用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()?或两者都在我的程序中?

3 个答案:

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