Python:如何在保持GUI交互性的同时更新来自不同进程的变量的GUI

2 投票
1 回答
2994 浏览
提问于 2025-04-18 14:41

我在这里读了很多关于多个进程、管道等的内容,但还没有找到答案,如果之前有人回答过,我在这里先道个歉。

我有一块外部硬件,想为它创建一个图形用户界面(GUI)。我希望这个界面能够不断更新来自外部硬件的数据,同时用户也能与界面进行互动。举个例子,我有一个增益参数,用来驱动一个条形图,而这个条形图需要不断更新的同时,我希望用户能点击一个按钮来触发某个动作。下面是一些示例代码。尽管我知道这里肯定有一些严重的错误,但实际上这个代码几乎能运行,只是“退出”按钮没有反应:

#!/usr/bin/env python`
# -*- coding: utf-8 -*-
# 2014-07-24 S. Petit

import matplotlib.pyplot as plt
from serial import Serial
import serial, socket, time, datetime, sys, struct
from datetime import datetime
import numpy as np
import shutil
import os
from random import randint
from Tkinter import *
from multiprocessing import *

dcbSerialPort = 'COM10'

def getGainLNA(pipeToParent):
    try:
        S_dcb = Serial(dcbSerialPort, 115200, timeout=.1)
        print 'Opened DCB at', dcbSerialPort
    except:
        print '\r\n'
        print '*************************************************'
        print 'ERROR: Unable to open', dcbSerialPort, 'serial connection.'
        print '*************************************************'
        print '\r\n'
        raw_input()
        exit()

    while True:
        promptFound = False
        PICreturn = ''
        S_dcb.write('gain\r')
        while not promptFound:
            PICreturn += S_dcb.read(S_dcb.inWaiting())
            if 'DCB>' in PICreturn:
                promptFound = True

        gainLNA = float(PICreturn[20:28].strip())
        gainLNA_scaled = int(100*(gainLNA/33))

        pipeToParent.send(gainLNA_scaled)

    return()

if __name__ == '__main__':

    gainUpdaterPipe, gainUpdaterPipeChild = Pipe()

    lnaGainUpdater = Process(target=getGainLNA, args=(gainUpdaterPipeChild,))
    lnaGainUpdater.start()

    root=Tk()
    root.title = 'AGC'

    while True:
        if gainUpdaterPipe.poll():
            gainLNA = gainUpdaterPipe.recv()
            print gainLNA

            quitButton = Button(text='Quit', command=quit)
            quitButton.grid(row=1, column=0)

            areaAGC = Canvas(width=120, height=100, bg='blue')
            objectAGC = areaAGC.create_polygon(20,20, gainLNA,20, gainLNA,50, 20,50, outline='green', fill='yellow')
            areaAGC.grid(row=0, column=0)

        root.update_idletasks()

感谢任何帮助……
Steve P

编辑:好的,在尝试使用@ebarr的例子后,我现在的代码是这样的。标签组件能够更新计数,但条形图却没有更新:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 2014-07-24 S. Petit

import matplotlib.pyplot as plt
from serial import Serial
import serial, socket, time, datetime, sys, struct
from datetime import datetime
import numpy as np
import shutil
import os
from random import randint
import Tkinter as tk
from multiprocessing import *

dcbSerialPort = 'COM10'

# count from 0 to infinity, writing the value to a pipe
def count(pipe,stop):
    ii = 0
    while not stop.is_set():
        ii+=1
        pipe.send(ii)
        time.sleep(1)

class UpdatingGUI(tk.Frame):
    def __init__(self,parent):
        tk.Frame.__init__(self,parent)
        self.parent = parent
        self.parent_pipe, self.child_pipe = Pipe()
        self.stop_event = Event()

        # label to show count value
        self.updating_int = tk.IntVar()
        self.updating_int.set(0)
        self.updating_lbl = tk.Label(self,textvariable=self.updating_int)
        self.updating_lbl.pack()

        # bargraph to show count value
        self.area_barGraph = tk.Canvas(width=120, height=100, bg='blue')
        self.bargraph = self.area_barGraph.create_polygon(10,10, (10+self.updating_int.get()),10, (10+self.updating_int.get()),20, 10,20, outline='green', fill='yellow')
        self.area_barGraph.pack()

        # button that will stay responsive to requests while count is on going
        self.quit_btn = tk.Button(self,text="Quit",command=self.quit)
        self.quit_btn.pack()

        # launch count as a process
        self.counter = Process(target=count,args=(self.child_pipe,self.stop_event))
        self.counter.start()

        # call an update method to check the pipe and update the label
        self.update()

    def quit(self):
        self.stop_event.set()
        self.parent.destroy()

    def update(self):
        # While the pipe has data, read and update the StringVar
        while self.parent_pipe.poll():
            self.updating_int.set(self.parent_pipe.recv())

        # set the update method to run again in 1 seconds time
        self.parent.after(1000,self.update)


def main():
    root = tk.Tk()
    gui = UpdatingGUI(root)
    gui.pack()
    root.mainloop()

# print __name__

if __name__ == "__main__":
    main()

1 个回答

2

你已经接近一个可行的解决方案了。正如上面评论中提到的,使用 tkinter 的 after 方法可以解决你大部分的问题。

下面是一个简单的例子,展示了一个独立的进程(运行一个简单的计数器)如何传递状态,以便更新你的图形界面(GUI):

import Tkinter as tk
from multiprocessing import Event,Process,Pipe
from time import sleep

# count from 0 to infinity, writing the value to a pipe
def count(pipe,stop):
    ii = 0
    while not stop.is_set():
        ii+=1
        pipe.send(ii)
        sleep(1)

class UpdatingGUI(tk.Frame):
    def __init__(self,parent):
        tk.Frame.__init__(self,parent)
        self.parent = parent
        self.parent_pipe, self.child_pipe = Pipe()
        self.stop_event = Event()

        # label to show count value
        self.updating_txt = tk.StringVar()
        self.updating_txt.set("Waiting...")
        self.updating_lbl = tk.Label(self,textvariable=self.updating_txt)
        self.updating_lbl.pack()

        # button that will stay responsive to requests while count is on going
        self.quit_btn = tk.Button(self,text="Quit",command=self.quit)
        self.quit_btn.pack()

        # launch count as a process
        self.counter = Process(target=count,args=(self.child_pipe,self.stop_event))
        self.counter.start()

        # call an update method to check the pipe and update the label
        self.update()

    def quit(self):
        self.stop_event.set()
        self.parent.destroy()

    def update(self):
        # While the pipe has data, read and update the StringVar
        while self.parent_pipe.poll():
            self.updating_txt.set(self.parent_pipe.recv())

        # set the update method to run again in 1 seconds time
        self.parent.after(1000,self.update)


def main():
    root = tk.Tk()
    gui = UpdatingGUI(root)
    gui.pack()
    root.mainloop()

if __name__ == "__main__":
    main()

更新

针对更新后的代码:你几乎完成了,唯一的问题是你只调用了一次图表创建器,而它需要像这样添加到你的 update 函数中:

def update(self):
    # While the pipe has data, read and update the StringVar                                                                                
    while self.parent_pipe.poll():
        self.updating_int.set(self.parent_pipe.recv())
    dx = self.updating_int.get()
    self.area_barGraph.create_polygon(10,10, (10+dx),10, (10+dx),20, 10,20, outline='green', fill='yellow')
    # set the update method to run again in 1 seconds time                                                                                  
    self.parent.after(1000,self.update)

这样可以确保每次 intVar 更新时,图表也会相应地更新。

撰写回答