2017-10-20 47 views
0

J'ai un programme python qui, sur simple pression d'un bouton, veut faire une certaine tâche dans un thread séparé pour empêcher la tâche de rendre l'interface utilisateur inachevée à utiliser time.sleep() dans la tâche en cours. J'ai un problème avec le thread où lorsque le wx.CallAfter() est utilisé avec pub.sendMessage(), j'obtiens une exception. J'ai besoin du sous-pub pour envoyer des informations entre les threads. Ci-dessous est un exemple du problème que je vois, le code ne fait pas ce que je veux vraiment mais il montre l'erreur de la même manière. Ce code crée un bouton qui lorsqu'il est pressé crée un thread qui crée un tuple et envoie ensuite la partie de la chaîne du tuple à la barre d'état du cadre:Threading avec lancer pubsub AssertionError: 'callableObj n'est pas appelable' dans 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() 

Quand je lance ce code et appuyez sur le bouton de l'état mises à jour bar, mais je vois aussi un retraçage comme indiqué ci-dessous:

TestGUI Showing Status Bar Update

Traceback:

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 

Je suis à une complète perte de la raison pour laquelle la barre d'état se met à jour comme prévu, mais une exception est-elle déclenchée? Y at-il quelque chose d'évident que j'ai manqué ici, je n'ai pas fait de threading auparavant et c'est ma première tentative d'interface graphique avec Python et wxPython. Je saute dans la partie profonde. J'ai vu des solutions où c'était un problème d'espace de noms mais je ne peux pas voir un conflit d'espace de noms ici.

EDIT: Corrections de grammaire.

Répondre

0

WX .CallAfter prend une fonction et ses arguments

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

jamais travaillé avec WX, mais voici comment la signature callAfter ressemble: [wxPython]: wx.CallAfter(callableObj, *args, **kw).

Vous devez passer la fonction/méthode (appelable) et ses arguments (position/mot-clé) séparément et numéros de télécommunication réellement (comme dans [Python]: class threading.Thread(group=None, target=None, name=None, args=(), kwargs={})).

Je suppose que vous voulez callAfter appeler sendMessage comme spécifié dans l'extrait:

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

Ensuite, votre ligne:

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

devrait être:

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