首页 > 解决方案 > wxpython CallAfter 不改变 GUI

问题描述

我需要一个开始和停止按钮来通过 while 循环连续读取数据,所以我在这里使用了第一个示例https://wiki.wxpython.org/LongRunningTasks。然后我也想连续更改图形,但我不能这样做来模拟我试图只更改 TextCtrl。所以我用 wx.CallAfter 在 TextCtrl 上写,我看到它“内部”发生了变化,但它没有更新框架。我也尝试使用 Update() 和 Refresh() 但这也不起作用。所以我不知道该怎么做,我已经搜索了所有我能做但不知道该怎么做的地方,所以任何帮助都非常感谢!

import nidaqmx
import wx
import wxmplot
import time
from threading import Thread
from numpy import mean, std
from nidaqmx.constants import AcquisitionType, DataTransferActiveTransferMode, TerminalConfiguration


ID_START = wx.NewId()
ID_STOP = wx.NewId()
EVT_RESULT_ID = wx.NewId()

def EVT_RESULT(win, func):
    """Define Result Event."""
    win.Connect(-1, -1, EVT_RESULT_ID, func)

class ResultEvent(wx.PyEvent):
    """Simple event to carry arbitrary result data."""
    def __init__(self, data):
        """Init Result Event."""
        wx.PyEvent.__init__(self)
        self.SetEventType(EVT_RESULT_ID)
        self.data = data

myEVT = wx.NewEventType()
EVT = wx.PyEventBinder(myEVT, 1)

class MyEvent(wx.PyCommandEvent):
    def __init__(self, evtType, id):
        wx.PyCommandEvent.__init__(self, evtType, id)
        myVal = None

    def SetMyVal(self, val):
        self.myVal = val

    def GetMyVal(self):
        return self.myVal

# janela para ler canais
class NewTaskWindow(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title=title, size=(1000,500))
        self.parent = parent
        self.title = title
        self.task_panel = wx.Panel(self, wx.ID_ANY, size = (1000,500))

        self.canais = ['Canal', 'Dev1/ai0','Dev1/ai1','Dev1/ai2','Dev1/ai3','Dev1/ai4','Dev1/ai5',
                       'Dev1/ai6','Dev1/ai7','Dev1/ai8','Dev1/ai9','Dev1/ai10','Dev1/ai11',
                       'Dev1/ai12','Dev1/ai13','Dev1/ai14','Dev1/ai15']

        self.cfgs = [TerminalConfiguration.DEFAULT,
                     TerminalConfiguration.DEFAULT,
                     TerminalConfiguration.DIFFERENTIAL,
                     TerminalConfiguration.NRSE,
                     TerminalConfiguration.PSEUDODIFFERENTIAL,
                     TerminalConfiguration.RSE]

        self.configs = ['Modo', 'DEFAULT', 'DIFFERENTIAL', 'NRSE', 'PSEUDODIFFERENTIAL', 'RSE']
        self.config1 = TerminalConfiguration.DEFAULT
        self.config2 = TerminalConfiguration.DEFAULT
        self.config3 = TerminalConfiguration.DEFAULT
        self.config4 = TerminalConfiguration.DEFAULT
        self.chan1 = 'canal1'
        self.chan2 = 'canal2'
        self.chan3 = 'canal3'
        self.chan4 = 'canal4'

        wx.Button(self.task_panel, ID_START, 'Start', pos=(10,10))
        wx.Button(self.task_panel, ID_STOP, 'Stop', pos=(10,90))
        self.status = wx.StaticText(self.task_panel, -1, '', pos=(800,0))

        self.lista_canais_1 = wx.ComboBox(self.task_panel, id=1, pos=(106,10), 
                                     size=(100,80), choices=self.canais, style=wx.CB_READONLY)
        self.lista_canais_1.SetSelection(0)

        self.lista_canais_2 = wx.ComboBox(self.task_panel, id=2, pos=(106,40), 
                                     size=(100,80), choices=self.canais, style=wx.CB_READONLY)
        self.lista_canais_2.SetSelection(0)

        self.lista_canais_3 = wx.ComboBox(self.task_panel, id=3, pos=(106,70), 
                                     size=(100,80), choices=self.canais, style=wx.CB_READONLY)
        self.lista_canais_3.SetSelection(0)

        self.lista_canais_4 = wx.ComboBox(self.task_panel, id=4, pos=(106,100), 
                                     size=(100,80), choices=self.canais, style=wx.CB_READONLY)
        self.lista_canais_4.SetSelection(0)

        self.lista_configs_1 = wx.ComboBox(self.task_panel, id=5, pos=(216,10), 
                                     size=(100,80), choices=self.configs, style= wx.CB_READONLY)
        self.lista_configs_1.SetSelection(0)

        self.lista_configs_2 = wx.ComboBox(self.task_panel, id=6, pos=(216,40), 
                                     size=(100,80), choices=self.configs, style= wx.CB_READONLY)
        self.lista_configs_2.SetSelection(0)

        self.lista_configs_3 = wx.ComboBox(self.task_panel, id=7, pos=(216,70), 
                                     size=(100,80), choices=self.configs, style= wx.CB_READONLY)
        self.lista_configs_3.SetSelection(0)

        self.lista_configs_4 = wx.ComboBox(self.task_panel, id=8, pos=(216,100), 
                                     size=(100,80), choices=self.configs, style= wx.CB_READONLY)
        self.lista_configs_4.SetSelection(0)

        self.check1 = wx.CheckBox(self.task_panel, wx.ID_ANY, "ON", pos=(330,10), size=(40,20))
        self.check2 = wx.CheckBox(self.task_panel, wx.ID_ANY, "ON", pos=(330,40), size=(40,20))
        self.check3 = wx.CheckBox(self.task_panel, wx.ID_ANY, "ON", pos=(330,70), size=(40,20))
        self.check4 = wx.CheckBox(self.task_panel, wx.ID_ANY, "ON", pos=(330,100), size=(40,20))

        self.input_pontos = wx.TextCtrl(self.task_panel , wx.ID_ANY, style = wx.TE_PROCESS_ENTER,
                                        value = "0", size =(90,20), pos = (10,70))

        self.media1_text = wx.TextCtrl(self.task_panel , wx.ID_ANY, style = wx.TE_READONLY,
                                        size =(90,20), pos = (420,12)) #<<<<-----------------------
        self.media2_text = wx.TextCtrl(self.task_panel , wx.ID_ANY, style = wx.TE_READONLY,
                                        size =(90,20), pos = (420,42))
        self.media3_text = wx.TextCtrl(self.task_panel , wx.ID_ANY, style = wx.TE_READONLY,
                                        size =(90,20), pos = (420,72))
        self.media4_text = wx.TextCtrl(self.task_panel , wx.ID_ANY, style = wx.TE_READONLY,
                                        size =(90,20), pos = (420,102))
        self.desvio1_text = wx.TextCtrl(self.task_panel , wx.ID_ANY, style = wx.TE_READONLY,
                                        size =(90,20), pos = (560,12))
        self.desvio2_text = wx.TextCtrl(self.task_panel , wx.ID_ANY, style = wx.TE_READONLY,
                                        size =(90,20), pos = (560,42))
        self.desvio3_text = wx.TextCtrl(self.task_panel , wx.ID_ANY, style = wx.TE_READONLY,
                                        size =(90,20), pos = (560,72))
        self.desvio4_text = wx.TextCtrl(self.task_panel , wx.ID_ANY, style = wx.TE_READONLY,
                                        size =(90,20), pos = (560,102))

        wx.StaticText(self.task_panel, wx.ID_ANY, "Nº Pontos:", (10,50))
        wx.StaticText(self.task_panel, wx.ID_ANY, "Média:", (380,12))
        wx.StaticText(self.task_panel, wx.ID_ANY, "Média:", (380,42))
        wx.StaticText(self.task_panel, wx.ID_ANY, "Média:", (380,72))
        wx.StaticText(self.task_panel, wx.ID_ANY, "Média:", (380,102))
        wx.StaticText(self.task_panel, wx.ID_ANY, "Desvio: ", (520,12))
        wx.StaticText(self.task_panel, wx.ID_ANY, "Desvio: ", (520,42))
        wx.StaticText(self.task_panel, wx.ID_ANY, "Desvio: ", (520,72))
        wx.StaticText(self.task_panel, wx.ID_ANY, "Desvio: ", (520,102))

        self.Bind(wx.EVT_COMBOBOX, self.SelectChannel, self.lista_canais_1)
        self.Bind(wx.EVT_COMBOBOX, self.SelectChannel, self.lista_canais_2)
        self.Bind(wx.EVT_COMBOBOX, self.SelectChannel, self.lista_canais_3)
        self.Bind(wx.EVT_COMBOBOX, self.SelectChannel, self.lista_canais_4)
        self.Bind(wx.EVT_COMBOBOX, self.SelectConfig, self.lista_configs_1)
        self.Bind(wx.EVT_COMBOBOX, self.SelectConfig, self.lista_configs_2)
        self.Bind(wx.EVT_COMBOBOX, self.SelectConfig, self.lista_configs_3)
        self.Bind(wx.EVT_COMBOBOX, self.SelectConfig, self.lista_configs_4)
        self.Bind(wx.EVT_BUTTON, self.OnStart, id=ID_START)
        self.Bind(wx.EVT_BUTTON, self.OnStop, id=ID_STOP)

        EVT_RESULT(self,self.OnResult)
        self.worker = None
        self.Bind(EVT, self.UpdateMedia1)


    def OnStart(self, event):                                #<<<<<<<<<<<--------------
        """Start Computation."""
        # Trigger the worker thread unless it's already busy
        global pontos, checks, chans, configz
        pontos=int(self.input_pontos.GetValue())
        checks = [self.check1.GetValue(), self.check2.GetValue(),
                  self.check3.GetValue(), self.check4.GetValue()]
        chans = [self.chan1, self.chan2, self.chan3, self.chan4]
        configz = [self.config1, self.config2, self.config3, self.config4]
        #self.media1_text.write('adsdsad')
        if len(chans) == len(set(chans)):
            self.graph_panel = wxmplot.plotpanel.PlotPanel(self.task_panel, pos=(0,150),
                                                           size=(500,350))
            self.graph_panel.plot([0],[0])
            if not self.worker:
                self.status.SetLabel('Starting computation')
                self.worker = WorkerThread(self, self.parent, self.title, 1)
        else:
            self.ErroCanais()

    def OnStop(self, event):
        """Stop Computation."""
        # Flag the worker thread to stop if running
        if self.worker:
            self.status.SetLabel('Trying to abort computation')
            self.worker.abort()

    def OnResult(self, event):
        """Show Result status."""
        if event.data is None:
            # Thread aborted (using our convention of None return)
            self.status.SetLabel('Computation aborted')
        else:
            # Process results here
            self.status.SetLabel('Computation Result: %s' % event.data)
        # In either event, the worker is done
        self.worker = None

    def UpdateMedia1(self, x):                # <<<<<<<----------------------
        self.media1_text.write(x)
        print(self.media1_text.GetValue()) 
        print(wx.IsMainThread())

    def ErroCanais(self):
        chan_erro_frame = wx.Frame(self, wx.ID_ANY, "ERRO", size=(200,100))
        chan_erro_panel = wx.Panel(chan_erro_frame, wx.ID_ANY, size=(200,100))
        wx.StaticText(chan_erro_panel, wx.ID_ANY, "CANAIS IGUAIS", (30,20))
        chan_erro_frame.Show()

    def SelectChannel(self, event):
        if event.GetId() == 1:
            if event.GetString() == 'Canal':
                self.chan1 = 'canal1'
            else:
                self.chan1 = event.GetString()
        elif event.GetId() == 2:
            if event.GetString() == 'Canal':
                self.chan2 = 'canal2'
            else:
                self.chan2 = event.GetString()
        elif event.GetId() == 3:
            if event.GetString() == 'Canal':
                self.chan3 = 'canal3'
            else:
                self.chan3 = event.GetString()
        elif event.GetId() == 4:
            if event.GetString() == 'Canal':
                self.chan4 = 'canal4'
            else:
                self.chan4 = event.GetString()

    def SelectConfig(self, event):
        if event.GetId() == 5:
            self.config1 = self.cfgs[event.GetSelection()]
        elif event.GetId() == 6:
            self.config2 = self.cfgs[event.GetSelection()]
        elif event.GetId() == 7:
            self.config3 = self.cfgs[event.GetSelection()]
        elif event.GetId() == 8:
            self.config4 = self.cfgs[event.GetSelection()]

class WorkerThread(Thread,NewTaskWindow):
    """Worker Thread Class."""
    def __init__(self, notify_window, parent, title, value):
        """Init Worker Thread Class."""
        Thread.__init__(self)
        NewTaskWindow.__init__(self, parent, title)
        self._notify_window = notify_window
        self._want_abort = 0
        # This starts the thread running on creation, but you could
        # also make the GUI thread responsible for calling this
        self.start()

    def run(self):
        """Run Worker Thread."""
        # This is the code executing in the new thread. Simulation of
        # a long process (well, 10s here) as a simple loop - you will
        # need to structure your processing so that you periodically
        # peek at the abort variable
        global pontos, checks, chans, configz
        #chann1, chann2, chann3, chann4 = "","","",""
        #chanz = [chann1, chann2, chann3, chann4]
        #task = nidaqmx.Task()
        #for i in range(4):
            #if checks[i]==True:
                #pass
#                task.ai_channels.add_ai_voltage_chan(chans[i],
#                                                     terminal_config=configz[i])
#                task.timing.cfg_samp_clk_timing(rate=10123, 
#                                                sample_mode=AcquisitionType.CONTINUOUS, 
#                                                samps_per_chan=10000)
#                chanz[i] = nidaqmx._task_modules.channels.ai_channel.AIChannel(task._handle,
#                                                                               chans[i])
#                chanz[i].ai_data_xfer_mech = DataTransferActiveTransferMode.INTERRUPT
        #data = []
        pontos = 10
        for i in range(pontos):
            #p = []
            #r = task.read(1)
            #p.append(r)
            #data.append(mean(p))
            #event = MyEvent(myEVT, self.GetId())
            #event.SetMyVal(i)
            #self.GetEventHandler().ProcessEvent(event)
            wx.CallAfter(self.UpdateMedia1, str(i))            #<<<<<<------------------
            time.sleep(1)
            if self._want_abort:
            # Use a result of None to acknowledge the abort (of
            # course you can use whatever you'd like or even
            # a separate event type)
                wx.PostEvent(self._notify_window, ResultEvent(None))
                return
        # Here's where the result would be returned (this is an
        # example fixed result of the number 10, but it could be
        # any Python object)
        #wx.PostEvent(self._notify_window, ResultEvent(10))

    def abort(self):
        """abort worker thread."""
        # Method for use by main thread to signal an abort
        self._want_abort = 1



class MainWindow(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title=title, size=(500,500))
        self.CreateStatusBar() # A StatusBar in the bottom of the window

        self.panel = wx.Panel(self, wx.ID_ANY)
        new_task_button = wx.Button(self.panel, wx.ID_ANY, 'New Task', (10, 10))

        # Setting up the menu.
        filemenu= wx.Menu()
        # wx.ID_ABOUT and wx.ID_EXIT are standard ids provided by wxWidgets.
        menuAbout = filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")
        menuExit = filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program")
        # Creating the menubar.
        menuBar = wx.MenuBar()
        menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar
        self.SetMenuBar(menuBar)  # Adding the MenuBar to the Frame content.

        self.v1_check = wx.CheckBox(self.panel, id=20, label="Valvula 1", pos=(20,50), size=(70,20))
        self.v2_check = wx.CheckBox(self.panel, id=21, label="Valvula 2", pos=(20,70), size=(70,20))
        self.v3_check = wx.CheckBox(self.panel, id=22, label="Valvula 3", pos=(20,90), size=(70,20))
        self.v4_check = wx.CheckBox(self.panel, id=23, label="Valvula 4", pos=(20,110), size=(70,20))
        self.v5_check = wx.CheckBox(self.panel, id=24, label="Porta 5", pos=(20,130), size=(70,20))
        self.v6_check = wx.CheckBox(self.panel, id=25, label="Porta 6", pos=(20,150), size=(70,20))
        self.v7_check = wx.CheckBox(self.panel, id=26, label="Porta 7", pos=(20,170), size=(70,20))
        self.v8_check = wx.CheckBox(self.panel, id=27, label="Porta 8", pos=(20,190), size=(70,20))
        self.v9_check = wx.CheckBox(self.panel, id=28, label="Porta 9", pos=(20,210), size=(70,20))
        self.v10_check = wx.CheckBox(self.panel, id=29, label="Porta 10", pos=(20,230), size=(70,20))

        # Set events.
        self.Bind(wx.EVT_BUTTON, self.OnNewTask, new_task_button)
        self.Bind(wx.EVT_CHECKBOX, self.AbrirFecharValvula, self.v1_check)
        self.Bind(wx.EVT_CHECKBOX, self.AbrirFecharValvula, self.v2_check)
        self.Bind(wx.EVT_CHECKBOX, self.AbrirFecharValvula, self.v3_check)
        self.Bind(wx.EVT_CHECKBOX, self.AbrirFecharValvula, self.v4_check)
        self.Bind(wx.EVT_CHECKBOX, self.AbrirFecharValvula, self.v5_check)
        self.Bind(wx.EVT_CHECKBOX, self.AbrirFecharValvula, self.v6_check)
        self.Bind(wx.EVT_CHECKBOX, self.AbrirFecharValvula, self.v7_check)
        self.Bind(wx.EVT_CHECKBOX, self.AbrirFecharValvula, self.v8_check)
        self.Bind(wx.EVT_CHECKBOX, self.AbrirFecharValvula, self.v9_check)
        self.Bind(wx.EVT_CHECKBOX, self.AbrirFecharValvula, self.v10_check)
        self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
        self.Bind(wx.EVT_MENU, self.OnExit, menuExit)

        self.Show(True)

    def AbrirFecharValvula(self, event):
        vchecksid=[20,21,22,23,24,25,26,27,28,29]
        vchecks = [self.v1_check,self.v2_check,self.v3_check,self.v4_check,self.v5_check,
                   self.v6_check,self.v7_check,self.v8_check,self.v9_check,self.v10_check]
        #num = str(vchecksid.index(event.GetId()))
        #porta = 'Dev1/port'+num+'/line1'
        for check in vchecks:
            if check.GetId()==event.GetId():
                #with nidaqmx.Task() as task:
                    #task.do_channels.add_do_chan(porta)
                    #task.write(check.GetValue())
                print(check.GetValue())

    def OnNewTask(self, event):
        newin = NewTaskWindow(self.panel, 'Task')
        newin.Show()

    def OnAbout(self,e):
        # A message dialog box with an OK button. wx.OK is a standard ID in wxWidgets.
        dlg = wx.MessageDialog( self, "A user interface with nidaqmx",
                               "About NIDAQMX GUI", wx.OK)
        dlg.ShowModal() # Show it
        dlg.Destroy() # finally destroy it when finished.

    def OnExit(self,e):
        self.Close(True)  # Close the frame.


# self.graph_panel.plot_many([(range(5),range(5)),(range(5),[0,0,0,0,0])])#[(x1,y1),(x2,y2)]    

if __name__=='__main__':
    app = wx.App(False)
    frame = MainWindow(None, "NIDAQMX GUI")
    frame.Centre()
    app.MainLoop()
    del app

我用 <<<<<----- 注释了代码的部分原因。您可以运行它来亲自查看 - 单击新任务,然后单击开始。所以 CallAfter 正在调用 UpdateMedia1 方法,它显示它在主线程中并且它正在改变 media1 textctrl 的值,但它在窗口中没有改变,我不明白为什么。我也尝试过创建自己的事件并使用 PostEvent ,但这也不起作用。我知道这是一个很长的问题,所以感谢您抽出宝贵的时间阅读!!

标签: pythonmultithreadinguser-interfacethread-safetywxpython

解决方案


您的WorkerThread类派生自,NewTaskWindow因此当您创建时,WorkerThread您还创建了一个新的不可见的NewTaskWindow

wx.CallAfter(self.UpdateMedia1, str(i))

theself指的是不可见的第二帧,因此它的(不可见的)文本控件被更新。

解决方案

不要继承NewTaskWindowWorkerThread调用UpdateMedia1可见框架对象的 (您在创建时已经将其作为参数提供WorkerThread)。


推荐阅读