从运行命令的单独线程更新Tkinter GUI

3

我试图在一个单独的线程中使用Tkinter打包一些东西,但是我一直遇到一个RuntimeError

具体来说:RuntimeError: main thread is not in main loop

问题是:我如何从单独的线程更新GUI(mainloop)?

问题的根源:

    def protocol(self, scheduleName): #ran as thread
        print("Getting courses")
        self.coursesLabel.pack() #throws the error
        ...

我希望能够将此线程中的项打包传输到mainloop()。我不确定如何在两个线程之间传输此内容。
谢谢你提前的帮助!
编辑:大多数关于此问题的答案都是针对同时运行两个独立的GUI的人。我只需要知道如何从一个线程中打包内容到另一个线程中,不需要像同时运行两个GUI那样复杂。大多数答案还使用了队列,我想知道是否有更优雅的方式来从不同的线程中打包东西。

这个网站上有16个问题提到了错误信息“主线程不在主循环中”,难道这些问题都没有帮到你吗? - Bryan Oakley
大多数这些问题都是针对同时运行两个独立的GUI的人。我只需要知道如何将一个线程中的东西打包到另一个线程中,而不是像同时运行两个GUI这样复杂的事情。大多数答案也使用了队列,我想知道是否有更优雅的方法来从不同的线程中打包东西。无论如何感谢您的回复! - A. Tolia
也许你应该更新你的问题,反映出你已经尝试过那些其他的解决方案或者看到它们并认为它们不适用,然后还要说你想知道是否有比使用队列更基本的东西。按照现在的写法,你的问题并没有展示出任何研究成果。 - Bryan Oakley
这是一个关于使用tkinter进行线程处理的良好背景介绍,由其中一位协助编写底层tcl/tk引擎的人撰写:https://stackoverflow.com/questions/38767355/callback-to-python-function-from-tkinter-tcl-crashes-in-windows/38767665#38767665 - Bryan Oakley
1个回答

4
在tkinter中,你可以使用event_generate从后台线程向GUI线程提交事件。这允许你更新小部件而不会出现线程错误。
1.在主线程中创建tkinter对象。 2.将root绑定到虚拟事件(即<>),指定一个事件处理程序。事件名称中需要使用尖括号。 3.启动后台线程。 4.在后台线程中使用event_generate触发主线程中的事件。使用state属性传递数据(数字)到事件。 5.在事件处理程序中处理事件。
以下是一个示例:
from tkinter import *
import datetime
import threading
import time

root = Tk()
root.title("Thread Test")
print('Main Thread', threading.get_ident())    # main thread id

def timecnt():  # runs in background thread
    print('Timer Thread',threading.get_ident())  # background thread id
    for x in range(10):
        root.event_generate("<<event1>>", when="tail", state=123) # trigger event in main thread
        txtvar.set(' '*15 + str(x))  # update text entry from background thread
        time.sleep(1)  # one second

def eventhandler(evt):  # runs in main thread
    print('Event Thread',threading.get_ident())   # event thread id (same as main)
    print(evt.state)  # 123, data from event
    string = datetime.datetime.now().strftime('%I:%M:%S %p')
    lbl.config(text=string)  # update widget
    #txtvar.set(' '*15 + str(evt.state))  # update text entry in main thread

lbl = Label(root, text='Start')  # label in main thread
lbl.place(x=0, y=0, relwidth=1, relheight=.5)

txtvar = StringVar() # var for text entry
txt = Entry(root, textvariable=txtvar)  # in main thread
txt.place(relx = 0.5, rely = 0.75, relwidth=.5, anchor = CENTER)

thd = threading.Thread(target=timecnt)   # timer thread
thd.daemon = True
thd.start()  # start timer loop

root.bind("<<event1>>", eventhandler)  # event triggered by background thread
root.mainloop()
thd.join()  # not needed

输出(注意主线程和事件线程是相同的)

Main Thread 5348
Timer Thread 33016
Event Thread 5348
......

我添加了一个Entry部件来测试能否从后台线程更新StringVar。对我来说,这起作用了,但如果您喜欢,可以在事件处理程序中更新字符串。请注意,从多个后台线程更新字符串可能会成为问题,因此应该使用线程锁。

请注意,如果后台线程自行退出,则没有错误。如果在其退出之前关闭应用程序,则会看到“主线程”错误。


谢谢,我已经实现了并且它起作用了!只是有一个小问题,我想要更新特定的GUI元素而不是创建新的,我该如何做呢?我尝试使用StringVar但它给了我一个字符串输出。 - A. Tolia
在示例代码中,标签小部件被更新。我添加了一个“Entry”小部件来测试“StringVar”,它似乎可以工作。 - Mike67

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接