在单独的线程中运行 Tkinter 表单

Posted

技术标签:

【中文标题】在单独的线程中运行 Tkinter 表单【英文标题】:Running a Tkinter form in a separate thread 【发布时间】:2012-05-20 08:27:31 【问题描述】:

我编写了一个简短的模块,它可以传递图像并简单地创建一个 Tkinter 窗口并显示它。我遇到的问题是,即使我实例化并调用在单独线程中显示图像的方法,主程序也不会继续,直到 Tkinter 窗口关闭。

这是我的模块:

import Image, ImageTk
import Tkinter


class Viewer(Tkinter.Tk):
    def __init__(self,parent):
        Tkinter.Tk.__init__(self,parent)
        self.parent = parent
        self.initialize()

    def initialize(self):
        self.grid()

    def show(self,img):
        self.to_display = ImageTk.PhotoImage(img)
        self.label_image = Tkinter.Label(self,image=self.to_display)
        self.label_image.grid(column = 0, row = 0, sticky = "NSEW")
        self.mainloop()

它似乎工作正常,除非我像下面这样从我的测试程序调用它,它似乎不允许我的测试程序继续,即使在不同的线程中启动也是如此。

import Image
from viewer import Viewer
import threading

def showimage(im):
    view = Viewer(None)
    view.show(im)

if __name__ == "__main__":
    im = Image.open("gaben.jpg")
    t = threading.Thread(showimage(im))
    t.start()
    print "Program keeps going..."

我认为我的问题可能是我应该在模块本身内创建一个新线程,但我只是想尽量保持简单,因为我是 Python 新手。

无论如何,提前感谢您的帮助。

编辑:为了清楚起见,我只是想制作一个在 Tkinter 窗口中显示图像的模块,以便我可以在任何时候使用这个模块来显示图像。我遇到的问题是,任何时候程序使用这个模块,在关闭 Tkinter 窗口之前它都无法恢复。

【问题讨论】:

只是提醒下一位访问者或 6 年后可能是 OP:除了 tkInter 和线程之外的主要问题是“threading.Thread(showimage(im))”不在线程中执行“showimage” , 但调用“showimage(im)”并将返回 (None) 作为参数提供给 Thread 构造函数。 "Thread(target=partial(showimage, im))" 将是正确的线程创建方式。 【参考方案1】:

Tkinter 不是线程安全的,普遍的共识是 Tkinter 不能在非主线程中工作。如果您重写代码以使 Tkinter 在主线程中运行,您可以让您的工作人员在其他线程中运行。

主要的警告是工人不能与 Tkinter 小部件交互。他们必须将数据写入队列,而您的主 GUI 线程必须轮询该队列。

如果您所做的只是显示图像,那么您可能根本不需要线程。线程仅在您有一个长时间运行的进程时才有用,否则会阻塞 GUI。 Tkinter 可以毫不费力地轻松处理数百个图像和窗口。

【讨论】:

非常感谢您的建议。我注意到如果我在查看器模块中运行 self.mainloop() ,就像上面的例子一样,它永远不会继续我的实际程序,直到窗口关闭。如果我将这一行放在程序中(例如 view.mainloop()),它会做同样的事情。我曾认为将这条线放在单独的线程中可以解决问题。你是说我应该不用线程就可以做到这一点,你的意思是有一种方法可以在没有 mainloop() 的情况下显示窗口吗? @ballsdotballs:如果您打算创建交互式 GUI,则需要致电 mainloop。就其本质而言,因为您正在运行一个事件循环,所以您可能不需要线程。确实,在调用mainloop 之后“它永远不会继续[您的] 实际程序”,但这就是它的设计方式。您建立循环,然后您的程序只响应事件。 mainloop 几乎总是应用程序启动逻辑中的最后一行代码。 我认为问题在于,我只是想写一个显示图像的模块。我需要一种方法让我的程序在使用此模块后继续运行,而不是等待用户关闭图像。你的意思是没有办法使用 Tkinter 来做到这一点? @ballsdotballs:一般来说,没有任何 GUI 工具包可以这样工作。 GUI 工具包具有事件循环并响应事件。话虽如此,您仍然可以在启动事件循环后运行其他代码,但是当该代码运行时,您的 GUI 将冻结。在不知道你真正想要完成的事情的情况下,很难更具体。我不知道它是否会有所帮助,但请查找 tkinter 的 after 方法的示例。有了它,你可以让事件循环在未来运行一些东西,这样你就可以用你的额外代码创建一个函数,然后在你调用 mainloop 几毫秒后运行它 非常感谢您提供的帮助。我知道我看起来很密集,但我认为你错过了我的基本问题。我根本不希望 GUI 做任何其他事情。它完全符合我的要求,即显示图像。我希望创建查看器的程序(上面的第二个块)能够在查看器运行时继续运行(就像我想在不关闭 GUI 的情况下以某种方式进入打印语句行)。【参考方案2】:

从您的 cmets 看来,您根本不需要 GUI。只需将图像写入磁盘并调用外部查看器即可。

在大多数系统上,应该可以使用以下方式启动默认查看器:

import subprocess 

subprocess.Popen("yourimage.png")

【讨论】:

在哪个系统上将图像文件作为进程执行?这真的试图直接执行文件,不涉及 shell,也没有查看文件类型或类似内容的代码。 我完全同意@BlackJack...这样做真的很危险...恶意软件可以模拟文件...您不应该直接运行它【参考方案3】:

据我所知,Tkinter 不喜欢玩其他线程。看到这个帖子...I Need a little help with Python, Tkinter and threading

解决方法是在主线程中创建一个(可能是隐藏的)顶层,生成一个单独的线程来打开图像等 - 并使用共享队列将消息发送回 Tk 线程。

您的项目需要使用 Tkinter 吗?我喜欢Tkinter。它“又快又脏”。 - 但在(很多)情况下,其他 GUI 工具包是可行的。

【讨论】:

您回答的第一部分很好,但由于建议尝试其他工具包,我无法投票。 Tkinter 非常适合这个问题所涉及的类型。【参考方案4】:

我试图从一个单独的线程运行 tkinter,这不是一个好主意,它会冻结。 有一种解决方案有效。在主线程中运行 gui,并将事件发送到主 gui。这是一个类似的例子,它只是显示一个标签。

import Tkinter as t
global root;
root = t.Tk()
root.title("Control center")
root.mainloop()

def new_window(*args):
    global root
    print "new window"
    window = t.Toplevel(root)
    label = t.Label(window, text="my new window")
    label.pack(side="top", fill="both", padx=10, pady=10)
    window.mainloop()

root.bind("<<newwin>>",new_window)

#this can be run in another thread
root.event_generate("<<newwin>>",when="tail")

【讨论】:

以上是关于在单独的线程中运行 Tkinter 表单的主要内容,如果未能解决你的问题,请参考以下文章

Python - 在单独的子进程或线程中运行 Autobahn|Python asyncio websocket 服务器

如何在 tkinter 窗口中打开 cv2 窗口

使用 PIL 在 Tkinter Canvas 小部件中嵌入图像

Python中tkinter控件中的Listbox控件详解

使用 python tkinter 在单独的窗口中显示 pdf

为啥 tkinter 不能很好地处理多处理?