【问题标题】:Tkinter exit command with infinite loop带有无限循环的 Tkinter 退出命令
【发布时间】:2015-11-07 23:20:39
【问题描述】:

我在使用 tkinter 的界面窗口的关闭按钮时遇到了一些问题。我的工具实时显示一些视频,我使用 after 函数无限循环来实现。

当我通过单击十字关闭 tkinter 窗口时,程序会冻结。但是,当我单击按钮时,会调用相同的函数但它会正确关闭。

这是我为向您展示问题而提出的最简化的代码。有人有解释和解决方法吗?

(顺便说一句,我在 OSX 上使用 Python 2.7.8)

from Tkinter import *
from PIL import Image, ImageTk
import numpy as np

class Test():
    def __init__(self, master):
        self.parent = master
        self.frame = Frame(self.parent)
        self.frame.pack(fill=BOTH, expand=1)
        self.mainPanel = Label(self.frame)
        self.mainPanel.pack(fill=BOTH, expand=1)
        self.closeButton = Button(self.frame, command=self.closeApp)
        self.closeButton.pack(fill=BOTH, expand=1)

    def closeApp(self):
        print "OVER"
        self.parent.destroy()

def task(tool):
    print 'ok'
    im = Image.fromarray(np.zeros((500, 500, 3)), 'RGB')
    tool.tkim = ImageTk.PhotoImage(im)
    tool.mainPanel['image'] = tool.tkim
    root.after(1, task, tool)

def on_closing():
    print "OVER"
    root.destroy()

root = Tk()
root.wm_protocol("WM_DELETE_WINDOW", on_closing)
tool = Test(root)
root.after(1, task, tool)
root.mainloop()

现在,如果您再次尝试使用较小的图像(例如 100*100),它可以工作。或者,如果您在 after 函数中放置 100 的延迟,它也可以工作。但是在我的应用程序中,我需要一个非常短的延迟时间,因为我正在显示一个视频并且我的图像大小是 900px*500px。

谢谢!

编辑(08/19):我还没有找到解决方案。但我可以使用root.overrideredirect(1) 删除关闭按钮,然后在 Tk 中重新创建它,并使用以下方法添加拖动窗口:Python/Tkinter: Mouse drag a window without borders, eg. overridedirect(1)

编辑(08/20):实际上,我什至不能拖动窗口。该工具也冻结了!

【问题讨论】:

  • stackoverflow.com/a/17016127/4731042 另见 Bryan Oakley:“我建议不要在 1ms 后使用 after,除非你真的需要每秒执行 1000 次。”在stackoverflow.com/questions/28047746/…
  • 感谢您的链接。就我而言,它无限期地冻结,可能是因为无限循环。如果只是为了任务函数的递归而冻结,那根本不是问题。布莱恩奥克利可能是对的。问题是我真的需要延迟

标签: python tkinter tk


【解决方案1】:

我找到了一个解决方案,我不确定它是否真的很干净,但至少它可以满足我的需求。 after 我不再使用,但我在每次迭代时循环并更新 gui。

from Tkinter import *
from PIL import Image, ImageTk
import numpy as np


class Test():
    def __init__(self, master):
        self.parent = master
        self.frame = Frame(self.parent)
        self.frame.pack(fill=BOTH, expand=1)
        self.mainPanel = Label(self.frame)
        self.mainPanel.pack(fill=BOTH, expand=1)
        self.parent.wm_protocol("WM_DELETE_WINDOW", self.on_closing)
        self.close = 0

    def on_closing(self):
        print "Over"
        self.close = 1

    def task(self):
        print "ok"
        im = Image.fromarray(np.zeros((500, 500, 3)), 'RGB')
        self.tkim = ImageTk.PhotoImage(im)
        self.mainPanel['image'] = self.tkim

root = Tk()
tool = Test(root)

while(tool.close != 1):
    tool.task()
    root.update()
root.destroy()

【讨论】:

    【解决方案2】:

    您可能只需要终止动画循环。 after 返回一个作业 ID,可用于取消挂起的作业。

    def task():
        global job_id
        ...
        job_id = root.after(1, task, tool)
    
    def on_closing():
        global job_id
        ...
        root.after_cancel(job_id)
    

    如果这些函数是对象的方法,您的代码可能会更简洁一些,因此您不必使用全局变量。此外,您应该有一个退出功能而不是两个。或者,让一个呼叫另一个,这样您就可以确定两者都通过完全相同的代码路径。

    最后,你不应该每秒调用一个函数 1000 次,除非你真的需要。如此频繁地调用它会使你的 UI 变得迟钝。

    【讨论】:

    • 这太好了,谢谢!并感谢您的提示。实际上我会用25 - app_time(其中app_time是我的图像处理时间)调用函数后,因为我需要视频在40 fps。
    • 很抱歉,它不起作用,对于某些测试,我放了 20ms 并且它起作用了,但是设置了一些较小的值,它不再起作用了。实际上,甚至没有调用 on_closure 函数:永远不会打印“OVER”。你有解决方案的想法吗?
    猜你喜欢
    • 1970-01-01
    • 2016-11-07
    • 2020-06-06
    • 2017-01-20
    • 1970-01-01
    • 2018-12-03
    • 2011-06-06
    • 2021-12-17
    相关资源
    最近更新 更多