I have a python program that on a push of a button I want to do a certain task in a separate thread to stop the task from making the GUI unresponsive due to using time.sleep() in the task in hand. I have an issue with the thread where when the wx.CallAfter() is used with pub.sendMessage() I get an exception. I need the pub sub to send information between the threads.
Below is an example of the issue I am seeing, the code isn't doing what I really want but it shows the error in the same way. This code creates a button that when pressed creates a thread that creates a tuple and then sends the string part of the tuple to the status bar on the frame:
#!/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()
When I run this code and press the button the status bar updates but I also see a Traceback as shown below:
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
I am at a complete loss as to why the status bar updates as expected but I get an Exception raised? Is there something obvious I have missed here, I haven't done threading before and this is my first GUI attempt with Python and wxPython. I am jumping in at the deep end. I have seen solutions where it was a namespace issue but I can't see a namespace conflict here.
EDIT: Grammar fixes.
Never worked with wx , but here's how callAfter
signature looks like: [wxPython]: wx.CallAfter ( callableObj , *args , **kw ) .
You have to pass the function/method (callable) and its arguments (positional/keyword) separately , and not actually call it (just like in [Python]: class threading. Thread ( group = None , target = None , name = None , args = () , kwargs = {} ) ).
I assume that you want callAfter
to call sendMessage
like you specified in the snippet:
pub.sendMessage("changeStatus", msg=testResults[1])
Then, your line:
wx.CallAfter(pub.sendMessage("changeStatus", msg=testResults[1]))
should be:
wx.CallAfter(pub.sendMessage, args=("changeStatus",), kw={"msg": testResults[1]})
wx.CallAfter接受一个函数及其参数
wx.CallAfter(pub.sendMessage,"changeStatus", msg=testResults[1])
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.