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

时间:2014-07-24 22:56:25

标签: python user-interface tkinter multiprocessing pipe

在这里读了很多关于多个流程,管道等的内容之后,我还没有找到答案,但是如果它已经存在我会道歉。

我有一块外围硬件,我试图创建一个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()

1 个答案:

答案 0 :(得分: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()

<强>更新

响应更新后的代码:您已经完成了,唯一的问题是您只需要调用bargraph创建器一次,而需要将其添加到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时,图表也会相应更新。