Skip to main content
 首页 » 编程设计

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

2024年10月25日32mq0036

在这里阅读了很多关于多进程、管道等的内容后,我还没有找到答案,但如果它已经存在,我深表歉意。

我有一个外围硬件,我正在尝试为其创建一个 GUI。我想让 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() 

感谢您的帮助... 史蒂夫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() 

请您参考如下方法:

您非常接近可行的解决方案。正如上面的评论之一所述,使用 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 时,图形也会相应地更新。