Tkinter 理解主循环

2024-11-22 08:47:00
admin
原创
6
摘要:问题描述:到目前为止,我习惯用以下方式结束我的 Tkinter 程序:tk.mainloop(),否则什么都不会显示!参见示例:from Tkinter import * import random import time tk = Tk() tk.title = "Game" tk.r...

问题描述:

到目前为止,我习惯用以下方式结束我的 Tkinter 程序: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()

但是,当我尝试执行此程序中的下一步(使球随时间移动)时,我所读的书却说要执行以下操作。因此,我将绘制函数更改为:

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()?还是两者都用?


解决方案 1:

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 循环可以说是阻塞的,就像tk.mainloop()因为 while 循环之后的任何内容都不会执行。

但是,tk.mainloop()这并不能替代以下几行:

tk.update_idletasks()
tk.update()

而是tk.mainloop()替代整个 while 循环:

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

回复评论:

以下是tcl 文档的内容:

更新 idletasks

update 的这个子命令会从 Tcl 的事件队列中清除所有当前安排的空闲事件。空闲事件用于推迟处理,直到“没有其他事情可做”,其典型用例是 Tk 的重绘和几何重新计算。通过将这些推迟到 Tk 空闲,昂贵的重绘操作将不会执行,直到事件集群(例如,按钮释放、当前窗口更改等)中的所有事件都在脚本级别处理完毕。这让 Tk 看起来快得多,但是如果您正在执行一些长时间运行的处理,这也可能意味着长时间内不会处理任何空闲事件。通过调用 update idletasks,将立即处理由于内部状态更改而导致的重绘。(由于系统事件(例如,被用户取消图标化)而导致的重绘需要进行完整更新才能处理。)

APN 正如在更新被认为有害中所述,使用更新来处理更新空闲任务未处理的重绘存在许多问题。Joe English 在 comp.lang.tcl 帖子中描述了一种替代方案:

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

来自更新文档:

更新?空闲任务?

更新命令用于通过反复进入 Tcl 事件循环直到所有待处理事件(包括空闲回调)都已处理,从而使应用程序“保持最新”。

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

KBK(2000 年 2 月 12 日)——我个人认为 [update] 命令不是最佳实践之一,建议程序员避免使用它。我很少甚至从未见过 [update] 的使用不能通过其他方式更有效地编程,通常是使用事件回调。顺便说一句,此警告适用于所有以递归方式进入事件循环的 Tcl 命令(vwait 和 tkwait 是其他常见的罪魁祸首),但使用全局级别的单个 [vwait] 在 shell 内启动事件循环(不会自动启动)除外。

我见过[更新]推荐的最常见用途是:

  1. 在执行某些长时间运行的计算时保持 GUI 处于活动状态。请参阅倒计时程序以了解替代方案。2) 等待窗口配置完成,然后再对窗口进行几何管理等操作。替代方案是绑定事件,例如通知进程窗口的几何形状。请参阅居中窗口以了解替代方案。

更新有什么问题?答案有多种。首先,它往往会使周围 GUI 的代码复杂化。如果您在 Countdown 程序中进行练习,您会感觉到当每个事件在其自己的回调中处理时,它会变得多么容易。其次,它是隐蔽错误的来源。一般问题是执行 [更新] 几乎有不受约束的副作用;从 [更新] 返回时,脚本很容易发现它下面的地毯被抽走了。在 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 编程中,必须不惜一切代价避免无限循环,以使小部件能够响应用户输入,例如鼠标单击。

那么,问题是:如何反复执行某些操作而不会真正创建无限循环? 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()

解决方案 2:

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 的设计用途。

解决方案 3:

我使用的是 MVC / MVA 设计模式,具有多种类型的“视图”。一种类型是“GuiView”,即 Tk 窗口。我将视图引用传递给窗口对象,该对象执行诸如将按钮链接回视图函数(适配器/控制器类也会调用该函数)之类的操作。

为了做到这一点,需要在创建窗口对象之前完成视图对象构造函数。在创建并显示窗口后,我想自动对视图执行一些初始任务。起初我尝试在 mainloop() 之后执行这些任务,但由于 mainloop() 被阻塞,所以没有成功!

因此,我创建了窗口对象并使用 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()
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   609  
  在现代项目管理中,资源的有效利用是确保项目成功的关键因素之一。随着技术的不断进步,越来越多的工具和软件被开发出来,以帮助项目经理和团队更高效地管理资源。本文将介绍10款工具,这些工具可以帮助项目团队提升资源利用效率,从而实现项目目标。禅道项目管理软件禅道项目管理软件是一款开源的项目管理工具,广泛应用于软件开发和其他行业...
项目管理系统   3  
  在项目管理领域,软件工具的不断升级和创新是推动效率和协作的关键。2024年,众多项目管理软件将迎来一系列令人期待的升级功能,这些新特性不仅将提升团队的工作效率,还将增强用户体验和数据分析能力。本文将详细介绍10款项目管理软件的最新升级功能,帮助项目经理和团队成员更好地规划和执行项目。禅道项目管理软件禅道项目管理软件一直...
开源项目管理工具   2  
  信创国产系统的10个关键厂商及其技术生态随着全球信息技术格局的不断演变,信创(信息技术应用创新)产业作为国产化替代的重要阶段,正逐步成为推动我国信息技术自主可控、安全可靠的核心力量。信创产业不仅关乎国家信息安全,也是数字经济高质量发展的关键支撑。本文将深入探讨信创国产系统中的10个关键厂商及其技术生态,分析它们在信创浪...
项目管理流程   0  
  在探讨项目管理的广阔领域中,成功并非偶然,而是精心策划、高效执行与持续优化的结果。项目管理的成功之道,可以从明确的目标设定与规划、高效的团队协作与沟通、以及灵活的风险管理与适应变化这三个核心方面进行深入解析。每个方面都是项目成功的基石,它们相互交织,共同支撑起项目的顺利推进与最终成就。明确的目标设定与规划项目管理的首要...
建筑工程项目管理规范   0  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用