基于串行数据的动态更新Tkinter窗口

2024-04-24 02:44:07 发布

您现在位置:Python中文网/ 问答频道 /正文

我正试图编写一个程序,从串行端口连接获取数据,并根据这些数据自动实时更新Tkinter窗口。

我试图为窗口创建一个单独的线程,该线程定期从主线程获取当前数据并更新窗口,如下所示:

serialdata = []
data = True

class SensorThread(threading.Thread):
    def run(self):
        serial = serial.Serial('dev/tty.usbmodem1d11', 9600)
        try:
            while True:
                serialdata.append(serial.readline())
        except keyboardInterrupt:
            serial.close()
            exit()

class GuiThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.root = Tk()
        self.lbl = Label(self.root, text="")

    def run(self):
        self.lbl(pack)
        self.lbl.after(1000, self.updateGUI)
        self.root.mainloop()

    def updateGUI(self):
        msg = "Data is True" if data else "Data is False"
        self.lbl["text"] = msg
        self.root.update()
        self.lbl.after(1000, self.updateGUI)

if __name == "__main__":
    SensorThread().start()
    GuiThread().start()

    try:
        while True:
            # A bunch of analysis that sets either data = True or data = False based on serialdata
    except KeyboardInterrupt:
        exit()

运行它会出现以下错误:

Exception in thread Thread-2: Traceback (most recent call last): File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/threading.py", line 522, in __bootstrap_inner self.run() File "analysis.py", line 52, in run self.lbl1.pack() File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/lib-tk/Tkinter.py", line 1764, in pack_configure + self._options(cnf, kw)) RuntimeError: main thread is not in main loop

当我在谷歌上搜索这个错误时,我得到的大多数帖子都是人们试图从两个不同的线程与窗口进行交互,但我不认为我在这么做。有什么想法吗?非常感谢!


Tags: runinselftruedatadefserialroot
2条回答

不要从线程运行tkgui—从主进程运行它。我把你的例子拼凑成一个能说明原理的东西

from time import sleep
import threading
from Tkinter import *

serialdata = []
data = True

class SensorThread(threading.Thread):
    def run(self):
        try:
            i = 0
            while True:
                serialdata.append("Hello %d" % i)
                i += 1
                sleep(1)
        except KeyboardInterrupt:
            exit()

class Gui(object):
    def __init__(self):
        self.root = Tk()
        self.lbl = Label(self.root, text="")
        self.updateGUI()
        self.readSensor()

    def run(self):
        self.lbl.pack()
        self.lbl.after(1000, self.updateGUI)
        self.root.mainloop()

    def updateGUI(self):
        msg = "Data is True" if data else "Data is False"
        self.lbl["text"] = msg
        self.root.update()
        self.lbl.after(1000, self.updateGUI)

    def readSensor(self):
        self.lbl["text"] = serialdata[-1]
        self.root.update()
        self.root.after(527, self.readSensor)

if __name__ == "__main__":
    SensorThread().start()
    Gui().run()

您需要将GUI放在主线程中,并使用单独的线程来轮询串行端口。从串行端口读取数据时,可以将其推送到队列对象上。

在主GUI线程中,可以通过使用after安排轮询来设置轮询以定期检查队列。调用一个函数,该函数将耗尽队列,然后使用after调用自身,以有效地模拟无限循环。

如果来自传感器的数据以相当慢的速度出现,并且您可以在不阻塞的情况下轮询串行端口,那么您可以在主线程中完成这一切—而不是从队列中推送和拉取,您的主线程可以只查看是否有可用的数据,如果有,则读取它。只有在可以不阻塞地读取时,才能执行此操作,否则在等待数据时,GUI将冻结。

例如,您可以让它像这样工作:

def poll_serial_port(self):
    if serial.has_data():
        data = serial.readline()
        self.lbl.configure(text=data)
    self.after(100, self.poll_serial_port)

上面将每秒检查串行端口10次,一次拔出一个项目。当然,你必须根据你的实际数据情况来调整它。这假设您有has_data这样的方法,只有在读不阻塞时才能返回True。

相关问题 更多 >