当前位置: 首页 > 知识库问答 >
问题:

Tkinter主回路

酆华皓
2023-03-14

到目前为止,我经常用:tk.mainloop()结束我的Tkinter程序,否则什么都不会出现!参见示例:

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

然而,当尝试这个程序中的下一步(让球按时间移动)时,我正在读的书说要做以下操作。因此,我将draw函数更改为:

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()(可能是因为它使用Python3),但我在网上搜索时了解到了它,因为我的程序无法通过复制书中的代码来工作!

所以我试着做了以下不可行的事情!!!

while 1:
    ball.draw()
    tk.mainloop()
    time.sleep(0.01)

发生什么事?什么是tk。mainloop()?什么是tk。更新_idletasks()tk。update()do以及它与tk的区别。mainloop()?我应该使用上面的循环吗<代码>tk。mainloop()?或者在我的程序中两者都有?

共有3个答案

宇文俊明
2023-03-14

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

为此,需要在创建窗口对象之前完成视图对象构造函数。在创建和显示窗口之后,我想用视图自动执行一些初始任务。起初,我试着在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()
白博赡
2023-03-14
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的设计用途。

穆毅然
2023-03-14

tk。mainloop()块。这意味着Python命令的执行在那里停止。您可以通过以下文字看到:

while 1:
    ball.draw()
    tk.mainloop()
    print("hello")   #NEW CODE
    time.sleep(0.01)

您将永远看不到print语句的输出。因为没有循环,球就不动了。

另一方面,这里的方法update\u idletasks()update()

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

...不要阻塞;这些方法完成后,执行将继续,因此while循环将反复执行,从而使球移动。

包含方法调用update\u idletasks()update()的无限循环可以替代调用tk。mainloop()。注意,整个while循环可以说是阻塞的,就像tk一样。mainloop(),因为while循环之后不会执行任何操作。

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

tk.update_idletasks()
tk.update()

更确切地说,tk.mainloop()是整个而循环的替代品:

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

对评论的答复:

以下是tcl文档的内容:

更新idletasks

update的这个子命令从Tcl的事件队列中刷新所有当前计划的空闲事件。空闲事件用于将处理延迟到“无事可做”为止,它们的典型用例是Tk的重画和几何体重新计算。通过将这些延迟到Tk空闲,直到在脚本级别处理事件集群(例如,按钮释放、当前窗口更改等)中的所有内容,才能执行昂贵的重画操作。这使得TK看起来快得多,但是如果你正在做一些长时间运行的处理,它也意味着长时间没有空闲事件被处理。通过调用update idletasks,将立即处理由于状态的内部更改而导致的重画。(由于系统事件(例如,被用户解除身份)而重绘,需要处理完整的更新。)

APN如更新中所述被认为是有害的,使用更新来处理未由更新idletask处理的重新绘制有许多问题。乔·英格利什在comp.lang.tcl帖子中描述了一种替代方案:

所以update_idletasks()会导致一些事件子集被处理,这些事件子集是date()导致被处理的。

来自更新文档:

更新?白痴?

更新命令用于通过重复输入Tcl事件循环来更新html" target="_blank">应用程序,直到所有挂起的事件(包括空闲回调)都被处理完毕。

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

KBK(2000年2月12日)-我个人的意见是,[更新]命令不是最佳实践之一,程序员最好避免使用它。我很少,如果曾经见过使用[更新]不能更有效地编程的另一种方式,通常是适当的使用事件回调。顺便说一下,这种警告适用于递归进入事件循环的所有Tcl命令(vwav和tkwav是其他常见的罪魁祸首),除了在全局级别使用单个[vwav]在不启动的shell中启动事件循环它自动。

我看到[update]推荐的最常见的用途是:

  1. 在执行某些长时间运行的计算时保持GUI的活动状态。请参阅倒计时程序以了解备选方案。2) 等待窗口配置,然后再对其执行几何图形管理等操作。另一种方法是绑定事件,例如通知窗口几何体进程的事件。有关备选方案,请参见将窗口居中

更新有什么问题?有几个答案。首先,它倾向于使周围GUI的代码复杂化。如果您在倒计时程序中进行这些练习,您将感受到当每个事件都在其自己的回调中处理时,它会变得多么容易。第二,它是潜伏性bug的来源。一般的问题是执行[update]有几乎不受限制的副作用;从[update]返回时,脚本可以很容易地发现地毯已经从下面拔出。在更新中,对这一现象的进一步讨论被认为是有害的。

.....

有没有可能我可以让我的程序工作,而不使用的时候循环?

是的,但是事情变得有点棘手。你可能会认为下面这样的东西会起作用:

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()会导致执行进入一个无限循环的Drew()方法,所以tk.mainloop()永远不会执行,你的小部件永远不会显示。在gui编程中,必须不惜一切代价避免无限循环,以保持小部件对用户输入的响应,例如鼠标点击。

所以,问题是:你如何一遍又一遍地执行某件事,而不实际创建一个无限循环?对于这个问题,Tkinter给出了一个答案:()方法之后的小部件的

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

后()方法不会阻塞(它实际上创建了另一个执行线程),所以在调用后()后,执行继续在Python程序中进行,这意味着tk.mainloop()下一步执行,所以您的小部件得到配置和显示。后()方法还允许您的小部件保持对其他用户输入的响应。尝试运行以下程序,然后在画布上的不同位置单击鼠标:

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

 类似资料:
  • 主要内容:创建一个空白窗口,设置窗的位置主窗口控件(window)是一切控件的基础,它好比是一台高速运转的机器,而其他控件则相当于这台机器上的部件,比如齿轮、链条、螺丝等等。由此我们知道,主窗口是一切控件的基础,所有的控件的都需要通过主窗口来显示。 Tkinter 提供了一些关于主窗口对象的常用方法,在本节对这些方法做简单的介绍。 创建一个空白窗口 Tkinter 能够很方便地创建一个空白窗口,示例代码如下: 程序运行结果如下: 图1:

  • 问题内容: 如何从作为Tkinter回调执行的函数中获取返回的对象? 显然,这是一个简化的示例。实际上,按钮调用的函数将返回对象,我希望将这些对象附加到将保留在主Python名称空间中的对象列表中,以进行进一步的操作。 无论如何,在这里用户可以使用GUI选择该函数的自变量,然后按下将执行该函数的按钮。但是,该函数的返回值似乎注定会丢失给以太,因为回调函数将不接受返回值。在不使用丑陋的定义的情况下可

  • 问题内容: 到现在为止,我以前以:结束我的Tkiter程序,否则什么都不会出现!参见示例: 但是,当尝试该程序的下一步(使球随着时间移动)时,该书正在阅读,并说要执行以下操作。将绘图功能更改为: 并将以下代码添加到我的程序中: 但是我注意到,添加此代码块使之无用,因为即使没有它,所有内容也会显示出来!!! 此时,我应该提到我的书从未谈论过(也许是因为它使用了Python 3),但是由于我的程序无法

  • 我有以下代码: 这里的主要循环是: 但是我不确定这是最好的方法(如果我想输入什么,这是行不通的) 然后我试了这个: 但正如我所意识到的,这并不像我预期的那么快。所以问题是:创建主循环的最佳方法是什么?

  • 问题内容: 我有以下代码: 这里的主要循环是: 但是我不确定这是做到这一点的最佳方法(如果我想输入一些信息,这将不起作用) 然后我尝试了这个: 但是,正如我已经意识到的那样,它并没有达到我的预期。所以问题是:创建主循环的最佳方法是什么? 问题答案: Tkinter为此提供了一个强大的工具,它被称为after。它旨在用作同步睡眠命令,但可以通过调用自身在mainloop内建立一个循环。 之后,是一个

  • 我正在为我的公司开发一个“多层”GUI来监控温度和状态。因为我是python编程新手,所以我的代码需要一些帮助。 代码由类构成。“Main”初始化主窗口(tkinter)并创建其他要显示的帧(如果需要)。除“canvas”外,其他每个类都是一个框架,它将显示不同的内容。 每个画布都包含一个图像和一些文本/可变文本。线程用于从数据库获取数据并更改画布中的文本。 每次线程访问画布并尝试更改文本或创建新