2017-10-20 121 views
0

我有一個python程序,在按下按鈕時我想在一個單獨的線程中執行某個任務來停止任務使GUI無法響應在任務中使用time.sleep()。我有一個線程問題,當wx.CallAfter()與pub.sendMessage()一起使用時,我得到一個異常。我需要pub sub在線程之間發送信息。線程與pubsub投擲AssertionError:'callableObj不可調用'在wxPython

下面是我看到的問題的一個例子,代碼沒有做我真正想要的,但它以相同的方式顯示錯誤。此代碼創建一個按鈕,在按下時創建創建一個元組的螺紋,然後將元組的串部分發送到框架上的狀態欄:

#!/usr/bin/env python2.7 

import wx 

from wx.lib.pubsub import pub 
from threading import Thread 

#==================================== 
# Main Application Frame Class 
#==================================== 
class MainFrame(wx.Frame): 
    """The main frame class for the application.""" 
    # MainFrame Constructor Method 
    def __init__(self, *args, **kwargs): 
     """Initialise the main application frame and bind events to event handlers.""" 
     wx.Frame.__init__(self, style=wx.DEFAULT_FRAME_STYLE^wx.RESIZE_BORDER, *args, **kwargs) 

     self.appStatusBar = self.CreateStatusBar() 
     self.panel = MainPanel(self) 

     # Set up the file menu 
     filemenu = wx.Menu() 
     menuAbout = filemenu.Append(wx.ID_ABOUT, "&About", " Testing Publisher with Threading") 
     menuExit = filemenu.Append(wx.ID_EXIT, "E&xit", " Terminate Program") 

     # Set up a menu bar for placing the file menu 
     menuBar = wx.MenuBar() 
     menuBar.Append(filemenu, "&File") 
     self.SetMenuBar(menuBar) 

     # Set the events that will trigger from the users interaction 
     self.Bind(wx.EVT_MENU, self.onAbout, menuAbout) 
     self.Bind(wx.EVT_MENU, self.onExit, menuExit) 

     # Use some sizers to see layout options 
     self.sizer = wx.BoxSizer(wx.VERTICAL) 
     self.sizer.Add(self.panel, proportion=1, flag=wx.EXPAND) 

     # Layout the sizers 
     self.SetSizer(self.sizer) 
     self.SetAutoLayout(1) 
     self.sizer.Fit(self) 

     # Set up listeners for the status bar and error dialog so that they can be implemented from other classes 
     pub.subscribe(self.changeStatusBar, "changeStatus") 
     pub.subscribe(self.errorMsgDisp, "errorDisplay") 

     self.Centre() 
     self.Show(True) 
    # End of MainFrame Constructor Method 

    # onAbout Method Functionality 
    def onAbout(self, e): 
     """Open Program About Dialog. 

     :param e: The event that triggered the method 
     """ 
     dlg = wx.MessageDialog(self, "Testing Publisher with Threading", "About Program", wx.OK) 
     dlg.ShowModal() 
     dlg.Destroy() 
    # End of onAbout() Method 

    # onExit Method Functionality 
    def onExit(self, e): 
     """Close the GUI down. 

     :param e: The event that triggered the method 
     """ 
     self.Close() 
    # End of onExit() Method 

    # Update the Status Bar Message Method 
    def changeStatusBar(self, msg): 
     """Change the message displayed on the status bar. 

     :param msg: Message to be displayed in the status bar 
     """ 
     self.appStatusBar.SetStatusText(msg) 
     self.appStatusBar.Refresh() 
    # End of changeStatusBar() Method 

    # Display Error Messages Method 
    def errorMsgDisp(self, msg): 
     """Display the error message sent to the function. 

     :param msg: The string with the error message to be displayed 
     """ 
     dlg = wx.MessageDialog(None, msg, "Error", wx.OK | wx.ICON_ERROR) 
     dlg.ShowModal() 
     dlg.Destroy() 
    # End of errorMsgDisp() Method 

# End of MainFrame class 


#==================================== 
# Main Panel Class 
#==================================== 
class MainPanel(wx.Panel): 
    """The main panel class for the application. 

    The main panel for holding all the widgets for the tool. 
    """ 
    # MainPanel Constructor Method 
    def __init__(self, parent, *args, **kwargs): 
     """Set up the main panel that all the widgets are tied to. 

     Defines all the widgets and events that are to occur when the widgets 
     are used. 

     :param parent: The parent frame/panel that the MainPanel belongs to 
     """ 
     wx.Panel.__init__(self, parent, *args, **kwargs) 

     self.mainVBox = wx.BoxSizer(wx.VERTICAL) 

     self.testingButton = wx.Button(self, label="Testing Button") 

     self.Bind(wx.EVT_BUTTON, self.onTestButton, self.testingButton) 

     # Add the COMs Panel to the main panel Vertical box sizer 
     self.mainVBox.Add(self.testingButton, proportion=1, flag=wx.EXPAND) 

     self.SetSizer(self.mainVBox) 

    # Event for doing something with the button 
    def onTestButton(self, e): 
     testBtn = e.GetEventObject() 
     testBtn.Disable() 

     testingThread = WorkerThread() 
     testingThread.start() 

# End of MainPanel class 

#==================================== 
# Activity Thread Class 
#==================================== 
class WorkerThread(Thread): 
    """Worker thread class for doing all time consuming tasks.""" 
    # WorkerThread Constructor Method 
    def __init__(self): 
     """Initialises the worker thread ready to run tasks.""" 
     Thread.__init__(self) 
    # End of WorkerThread Constructor Method 

    # Worker Run Method 
    def run(self): 
     """When the thread is started the tasks in this method are executed.""" 
     self.testButton()  
    # End of run() Method 

    # Test the button 
    def testButton(self): 
     """Create tuple and publish the string to the status bar""" 
     testResults = (0, "Status Bar Updated") 
     wx.CallAfter(pub.sendMessage("changeStatus", msg=testResults[1])) 
    # End of testButton() Method 

# End of WorkerThread Class 


#==================================== 
# Main Code that Runs the GUI 
#==================================== 
if __name__ == '__main__': 
    app = wx.App(False) 
    frame = MainFrame(None, title="Threading Test") 
    app.MainLoop() 

當運行該代碼,並按下該按鈕的狀態酒吧更新,但是我也看到了回溯,如下圖所示:

TestGUI Showing Status Bar Update

回溯:

Exception in thread Thread-1: 
Traceback (most recent call last): 
    File "C:\Python27\lib\threading.py", line 801, in __bootstrap_inner 
    self.run() 
    File "C:\Users\Mhaines\Documents\threading_pubsub_test.py", line 150, in run 
    self.testButton() 
    File "C:\Users\Mhaines\Documents\threading_pubsub_test.py", line 157, in testButton 
    wx.CallAfter(pub.sendMessage("changeStatus", msg=testResults[1])) 
    File "C:\Python27\lib\site-packages\wx-3.0-msw\wx\_core.py", line 16759, in CallAfter 
    assert callable(callableObj), "callableObj is not callable" 
AssertionError: callableObj is not callable 

我是在一個完整的關於爲什麼狀態欄更新如預期,但我得到異常提出?有沒有什麼明顯的我在這裏錯過了,我以前沒有做過線程化處理,這是我第一次使用Python和wxPython進行GUI嘗試。我在深層跳躍。我已經看到解決方案,它是一個名稱空間問題,但我不能在這裏看到名稱空間衝突。

編輯:語法修復。

回答

0

WX .CallAfter接受一個函數及其參數

wx.CallAfter(pub.sendMessage,"changeStatus", msg=testResults[1]) 
0

決不WX工作,但這裏的callAfter簽名的樣子: [wxPython]: wx.CallAfter(callableObj, *args, **kw)

你要通過函數/方法(可調用)和它的參數(位置/關鍵字)分別,而實際上不叫(就像在[Python]: class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}))。

我認爲你要callAfter調用sendMessage您在片段中指定,如:

pub.sendMessage("changeStatus", msg=testResults[1]) 

然後,你行:

wx.CallAfter(pub.sendMessage("changeStatus", msg=testResults[1])) 

應該是:

wx.CallAfter(pub.sendMessage, args=("changeStatus",), kw={"msg": testResults[1]})