How to a thread a wxpython progress bar GUI? - python

I am trying to figure out how to add a progress bar to a GUI Installer I am making. The problem is actually making the progress bar work. I have it implemented but it freezes the entire program half way though.
# Import Libraries
import requests, os, sys, zipfile, shutil, subprocess, wx, urllib, time
from threading import *
# Define variables
url = "Enter any dropbox link .zip file here"
r = requests.get(url, stream = True)
# Button definitions
ID_START = wx.NewId()
# Define notification event for thread completion
EVT_RESULT_ID = wx.NewId()
# Checks for old files
def Check():
if os.path.exists("Folder"):
print('\n\nRemoving old files...')
subprocess.check_call(('attrib -R ' + 'Folder' + '\\* /S').split())
shutil.rmtree('Folder')
print('\nRemoved old files.')
else:
pass
# Downloads new file
def Download():
print('\n\nDownloading:')
urllib.request.urlretrieve(url, 'temp.zip')
print('\nDownload Complete.')
# Extracts new file
def Extract():
print('\n\nExtracting...')
zip_ref = zipfile.ZipFile("temp.zip", 'r')
zip_ref.extractall("Folder")
zip_ref.close()
print('\nExtraction Complete')
# Deletes the .zip file but leave the folder
def Clean():
print('\n\nCleaning up...')
os.remove("temp.zip")
print('\nDone!')
# Thread class that executes processing
class WorkerThread(Thread):
"""Worker Thread Class."""
def __init__(self, notify_window):
"""Init Worker Thread Class."""
Thread.__init__(self)
self._notify_window = notify_window
# This starts the thread running on creation.
self.start()
# This is what runs on a separate thread when you click the download button
def run(self):
"""Run Worker Thread."""
# This is the code executing in the new thread.
Check()
Download()
Extract()
Clean()
# GUI Frame class that spins off the worker thread
class MainFrame(wx.Frame):
"""Class MainFrame."""
def __init__(self, parent, id):
"""Create the MainFrame."""
wx.Frame.__init__(self, parent, id, 'RFMP GUInstaller',
style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
self.SetSize(400, 350)
wx.Button(self, ID_START, 'Download', size=(300,50), pos=(42,250))
self.Bind(wx.EVT_BUTTON, self.OnStart, id=ID_START)
self.status = wx.StaticText(self, -1, '', pos=(7,200))
self.gauge = wx.Gauge(self, range = 1000, size = (370, 30), pos=(7,217),
style = wx.GA_HORIZONTAL)
# And indicate we don't have a worker thread yet
self.worker = None
def OnStart(self, event):
"""Start Computation."""
self.count = 0
# Trigger the worker thread unless it's already busy
if not self.worker:
self.status.SetLabel('Downloading...')
self.worker = WorkerThread(self)
while self.count <= 10000:
time.sleep(.001);
self.count = self.count + 1
self.gauge.SetValue(self.count)
self.status.SetLabel('Done!')
def OnResult(self, event):
"""Show Result status."""
# The worker is done
self.worker = None
class MainApp(wx.App):
"""Class Main App."""
def OnInit(self):
"""Init Main App."""
self.frame = MainFrame(None, -1)
self.frame.Show(True)
self.SetTopWindow(self.frame)
return True
# Main Loop
if __name__ == '__main__':
app = MainApp(0)
app.MainLoop()
Also, if you see any way to improve or simplify my code without minimizing efficiency, feel free.

Ok, firstly the freeze is due to the use of the sleep method.
You're spawning the new thread on the 'Download' button's click event, that's good. But, you have to make this thread somehow communicate back to the main thread/frame instead of sleeping in the main thread.
This is where an wx Event can be used. A good tutorial is here. Add something like this after the Clean() method:
myEVT_PROGRESS = wx.NewEventType() # Custom Event Type
EVT_PROGRESS = wx.PyEventBinder(myEVT_PROGRESS, 1) # bind specific events to event handlers
class ProgressEvent(wx.PyCommandEvent):
"""Event to signal that a status or progress changed"""
def __init__(self, etype, eid, status=None, progress=None):
"""Creates the event object"""
wx.PyCommandEvent.__init__(self, etype, eid)
self._status = status # field to update label
self._progress = progress # field to update progress bar
def GetValue(self):
"""Returns the value from the event.
#return: the tuple of status and progress
"""
return (self._status, self._progress)
Now the worker thread becomes a bit more complex since it has to notify the main thread on any progress:
# Thread class that executes processing
class WorkerThread(Thread):
"""Worker Thread Class."""
def __init__(self, notify_window):
"""Init Worker Thread Class."""
Thread.__init__(self)
self._notify_window = notify_window
self.sendEvent('started')
# This starts the thread running on creation.
self.start()
# This is what runs on a separate thread when you click the download button
def run(self):
# This is the code executing in the new thread.
self.sendEvent('checking', 0)
# Check() # this method isn't working for me...?
self.sendEvent('Downloading...', 100)
Download()
self.sendEvent('Downloading complete', 400)
# ... same pattern as above for other methods...
Extract()
Clean()
def sendEvent(self, status=None, progress=None):
# Send event to main frame, first param (str) is for label, second (int) for the progress bar
evt = ProgressEvent(myEVT_PROGRESS, -1, status, progress)
wx.PostEvent(self._notify_window, evt)
Now, all that's left is to receive the events on the main thread now.
class MainFrame(wx.Frame):
"""Class MainFrame."""
def __init__(self, parent, id):
# ...same as before...
self.Bind(EVT_PROGRESS, self.OnResult) # Bind our new custom event to a function
def OnStart(self, event):
# Trigger the worker thread unless it's already busy
if not self.worker:
self.status.SetLabel('')
self.worker = WorkerThread(self)
def OnResult(self, event):
"""Our handler for our custom progress event."""
status, progress = event.GetValue()
self.status.SetLabel(status)
if progress:
self.gauge.SetValue(progress)
Hope that makes sense.

I have put together your code, the excellent answer from Sree and added the use of the urllib.request.urlretrieve() reporthook option to display download completion.
Note: Any credit should be given to Sree this is just a personal exercise on my part.
# Import Libraries
import requests, os, sys, zipfile, shutil, subprocess, wx, urllib, time
from threading import *
# Define variables
url = "Enter any dropbox link .zip file here"
#r = requests.get(url, stream = True)
# Button definitions
ID_START = wx.NewId()
myEVT_PROGRESS = wx.NewEventType() # Custom Event Type
EVT_PROGRESS = wx.PyEventBinder(myEVT_PROGRESS, 1) # bind specific events to event handlers
class ProgressEvent(wx.PyCommandEvent):
"""Event to signal that a status or progress changed"""
def __init__(self, etype, eid, status=None, progress=None):
"""Creates the event object"""
wx.PyCommandEvent.__init__(self, etype, eid)
self._status = status # field to update label
self._progress = progress # field to update progress bar
def GetValue(self):
"""Returns the value from the event.
#return: the tuple of status and progress
"""
return (self._status, self._progress)
# Checks for old files
def Check():
if os.path.exists("Folder"):
print('\n\nRemoving old files...')
#subprocess.check_call(('attrib -R ' + 'Folder' + '\\* /S').split())
#shutil.rmtree('Folder')
print('\nRemoved old files.')
else:
pass
# Extracts new file
def Extract():
print('\n\nExtracting...')
#zip_ref = zipfile.ZipFile("temp.zip", 'r')
#zip_ref.extractall("Folder")
#zip_ref.close()
time.sleep(5)
print('\nExtraction Complete')
# Deletes the .zip file but leave the folder
def Clean():
print('\n\nCleaning up...')
#os.remove("temp.zip")
print('\nDone!')
# Thread class that executes processing
class WorkerThread(Thread):
"""Worker Thread Class."""
def __init__(self, notify_window):
"""Init Worker Thread Class."""
Thread.__init__(self)
self._notify_window = notify_window
# This starts the thread running on creation.
self.start()
# This is what runs on a separate thread when you click the download button
def run(self):
"""Run Worker Thread."""
# This is the code executing in the new thread.
self.SendEvent('Checking...', 50)
Check()
self.SendEvent('Connecting to download...', 0)
#Perform download
urllib.request.urlretrieve(url, 'temp.zip', reporthook=self.Download_Progress)
self.SendEvent('Extracting...', 800)
Extract()
self.SendEvent('Cleaning...', 900)
Clean()
self.SendEvent('Finished...', 1000)
def Download_Progress(self, block_num, block_size, total_size):
downloaded = block_num * block_size
progress = int((downloaded/total_size)*1000)
if progress > 1000:
progress = 1000
self.SendEvent("Download active...",progress)
def SendEvent(self, status=None, progress=None):
# Send event to main frame, first param (str) is for label, second (int) for the progress bar
evt = ProgressEvent(myEVT_PROGRESS, -1, status, progress)
wx.PostEvent(self._notify_window, evt)
# GUI Frame class that spins off the worker thread
class MainFrame(wx.Frame):
"""Class MainFrame."""
def __init__(self, parent, id):
"""Create the MainFrame."""
wx.Frame.__init__(self, parent, id, 'RFMP GUInstaller',
style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
self.SetSize(400, 350)
wx.Button(self, ID_START, 'Download', size=(300,50), pos=(42,250))
self.Bind(wx.EVT_BUTTON, self.OnStart, id=ID_START)
self.status = wx.StaticText(self, -1, '', pos=(7,200))
self.gauge = wx.Gauge(self, range = 1000, size = (370, 30), pos=(7,217),
style = wx.GA_HORIZONTAL)
# And indicate we don't have a worker thread yet
self.worker = None
self.Bind(EVT_PROGRESS, self.OnResult) # Bind our new custom event to a function
def OnStart(self, event):
"""Start Computation."""
self.count = 0
# Trigger the worker thread unless it's already busy
if not self.worker:
self.worker = WorkerThread(self)
def OnResult(self, event):
"""Show Result status."""
# The worker is done
self.worker = None
status, progress = event.GetValue()
self.status.SetLabel(status)
if progress:
self.gauge.SetValue(progress)
class MainApp(wx.App):
"""Class Main App."""
def OnInit(self):
"""Init Main App."""
self.frame = MainFrame(None, -1)
self.frame.Show(True)
self.SetTopWindow(self.frame)
return True
# Main Loop
if __name__ == '__main__':
app = MainApp(0)
app.MainLoop()

Related

QThread not work correctly and pyqt gui application freeze

I'm writing python gui application that process a image and send image color from serial port and show the results but unfortunately my gui freeze. i try using QApllication.processEvents and it is work but my program speed slow and speed is so important to me and every one second one iteration should be complete And then i use QThread and my application still freeze. Here is my code:
class Worker(QObject):
progress = pyqtSignal(int)
gui_update = pyqtSignal()
def __init__(self, knot, lay, baft):
super().__init__()
self.knot = knot
self.lay = lay
self.baft = baft
def run(self):
while self.knot <= knotter_width:
color_to_send = []
for i in range(1, number_of_knotter + 1):
color_to_send.append(self.baft["knotters"][i][self.lay][self.knot])
self.progress.emit(self.knot)
self.gui_update.emit() # for updating gui but not work
QThread setups:
self.thrd = QThread()
self.worker = Worker(self.knot, self.lay, self.baft)
self.worker.moveToThread(self.thrd)
self.thrd.started.connect(self.worker.run)
self.worker.progress.connect(self.progress)
self.worker.gui_update.connect(self.knotters_status)
self.worker.finish.connect(self.finished)
self.worker.ex.connect(self.thrd.quit)
self.worker.ex.connect(self.worker.deleteLater)
self.thrd.finished.connect(self.thrd.deleteLater)
self.thrd.start()
After three days of research i found that after setting up thread you need to call processEvents() just after creating thread object and in that situation it doesn't cause slowing down and every thing work perfectly.
note: you should put the parameters in init function.
this is my final code:
class Thrd(QThread):
progress = pyqtSignal(int)
gui_update = pyqtSignal()
finish = pyqtSignal(bool)
ex = pyqtSignal()
def __init__(self, knot, lay, baft):
super().__init__()
self.knot = knot
self.lay = lay
self.baft = baft
def run(self):
while condition:
# some process
self.progress.emit(self.knot)
self.gui_update.emit()
time.sleep(0.05) # give a little time to update gui
self.finish.emit(True)
self.ex.emit()
And setting up part:
self.thrd = Thrd(self.knot, self.lay, self.baft)
app.processEvents()
self.thrd.progress.connect(self.progress)
self.thrd.gui_update.connect(self.knotters_status)
self.thrd.finish.connect(self.finished)
self.thrd.ex.connect(self.thrd.quit)
self.thrd.finished.connect(self.thrd.deleteLater)
self.thrd.finished.connect(self.stop)
self.thrd.start()

Call method to the main thread 'from a sub thread'

I am making a data acquisition program that communicates with a measurement device. The status of the device needs to be checked periodically (e.g., every 0.1 sec) to see if acquisition is done. Furthermore, the program must have the 'abort' method because acquisition sometime takes longer than few minutes. Thus I need to use multi-threading.
I attached the flow-chart and the example code. But I have no idea how I call the main-thread to execute a method from the sub-thread.
python 3.7.2
wxpython 4.0.6
Flow Chart
import wx
import time
from threading import Thread
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Test Frame")
panel = wx.Panel(self)
self.Btn1 = wx.Button(panel, label="Start Measurement")
self.Btn1.Bind(wx.EVT_BUTTON, self.OnStart)
self.Btn2 = wx.Button(panel, label="Abort Measurement")
self.Btn2.Bind(wx.EVT_BUTTON, self.OnAbort)
self.Btn2.Enable(False)
self.DoneFlag = False
self.SubThread = Thread(target=self.Check, daemon=True)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.Btn1, 0, wx.EXPAND)
sizer.Add(self.Btn2, 0, wx.EXPAND)
panel.SetSizer(sizer)
def OnStart(self, event):
# self.N is the number of data points
self.N = 0
# self.N_max is the number of data points that is going to be acquired
self.N_max = int(input("How many data points do yo want? (greater than 1) : "))
self.DoneFlag = False
self.Btn1.Enable(False)
self.Btn2.Enable(True)
self.Start()
def OnAbort(self, event):
self.DoneFlag = True
def Start(self):
self.SubThread.start()
def Done(self):
if self.DoneFlag is True:
self.Finish()
elif self.DoneFlag is False:
self.Start()
def Finish(self):
print("Measurement done (N = {})\n".format(self.N))
self.Btn1.Enable(True)
self.Btn2.Enable(False)
def Check(self):
# In the actual program, this method communicates with a data acquisition device to check its status
# For example,
# "RunningStatus" is True when the device is still running (acquisition has not been done yet),
# is False when the device is in idle state (acquisition has done)
#
# [Structure of the actual program]
# while True:
# RunningStatus = GetStatusFromDevice()
# if RunningStatus is False or self.DoneFlag is True:
# break
# else:
# time.sleep(0.1)
# In below code, it just waits 3 seconds then assumes the acqusition is done
t = time.time()
time.sleep(1)
for i in range(3):
if self.DoneFlag is True:
break
print("{} sec left".format(int(5-time.time()+t)))
time.sleep(1)
# Proceed to the next steps after the acquisition is done.
if self.DoneFlag is False:
self.N += 1
print("Data acquired (N = {})\n".format(self.N))
if self.N == self.N_max:
self.DoneFlag = True
self.Done() # This method should be excuted in the main thread
if __name__ == "__main__":
app = wx.App()
frame = TestFrame()
frame.Show()
app.MainLoop()
When using a GUI it is not recommended to call GUI functions from another thread, see:
https://docs.wxwidgets.org/trunk/overview_thread.html
One of your options, is to use events to keep track of what is going on.
One function creates and dispatches an event when something happens or to denote progress for example, whilst another function listens for and reacts to a specific event.
So, just like pubsub but native.
Here, I use one event to post information about progress and another for results but with different targets.
It certainly will not be an exact fit for your scenario but should give enough information to craft a solution of your own.
import time
import wx
from threading import Thread
import wx.lib.newevent
progress_event, EVT_PROGRESS_EVENT = wx.lib.newevent.NewEvent()
results_event, EVT_RESULTS_EVENT = wx.lib.newevent.NewEvent()
class ThreadFrame(wx.Frame):
def __init__(self, title, parent=None):
wx.Frame.__init__(self, parent=parent, title=title)
panel = wx.Panel(self)
self.parent = parent
self.btn = wx.Button(panel,label='Stop Measurements', size=(200,30), pos=(10,10))
self.btn.Bind(wx.EVT_BUTTON, self.OnExit)
self.progress = wx.Gauge(panel,size=(240,10), pos=(10,50), range=30)
#Bind to the progress event issued by the thread
self.Bind(EVT_PROGRESS_EVENT, self.OnProgress)
#Bind to Exit on frame close
self.Bind(wx.EVT_CLOSE, self.OnExit)
self.Show()
self.mythread = TestThread(self)
def OnProgress(self, event):
self.progress.SetValue(event.count)
#or for indeterminate progress
#self.progress.Pulse()
if event.result != 0:
evt = results_event(result=event.result)
#Send back result to main frame
try:
wx.PostEvent(self.parent, evt)
except:
pass
def OnExit(self, event):
if self.mythread.isAlive():
self.mythread.terminate() # Shutdown the thread
self.mythread.join() # Wait for it to finish
self.Destroy()
class TestThread(Thread):
def __init__(self,parent_target):
Thread.__init__(self)
self.parent = parent_target
self.stopthread = False
self.start() # start the thread
def run(self):
curr_loop = 0
while self.stopthread == False:
curr_loop += 1
# Send a result every 3 seconds for test purposes
if curr_loop < 30:
time.sleep(0.1)
evt = progress_event(count=curr_loop,result=0)
#Send back current count for the progress bar
try:
wx.PostEvent(self.parent, evt)
except: # The parent frame has probably been destroyed
self.terminate()
else:
curr_loop = 0
evt = progress_event(count=curr_loop,result=time.time())
#Send back current count for the progress bar
try:
wx.PostEvent(self.parent, evt)
except: # The parent frame has probably been destroyed
self.terminate()
def terminate(self):
evt = progress_event(count=0,result="Measurements Ended")
try:
wx.PostEvent(self.parent, evt)
except:
pass
self.stopthread = True
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.text_count = 0
self.thread_count = 0
self.parent=parent
btn = wx.Button(self, wx.ID_ANY, label='Start Measurements', size=(200,30), pos=(10,10))
btn.Bind(wx.EVT_BUTTON, self.Thread_Frame)
btn2 = wx.Button(self, wx.ID_ANY, label='Is the GUI still active?', size=(200,30), pos=(10,50))
btn2.Bind(wx.EVT_BUTTON, self.AddText)
self.txt = wx.TextCtrl(self, wx.ID_ANY, style= wx.TE_MULTILINE, pos=(10,90),size=(400,100))
#Bind to the result event issued by the thread
self.Bind(EVT_RESULTS_EVENT, self.OnResult)
def Thread_Frame(self, event):
self.thread_count += 1
frame = ThreadFrame(title='Measurement Task '+str(self.thread_count), parent=self)
def AddText(self,event):
self.text_count += 1
txt = "Gui is still active " + str(self.text_count)+"\n"
self.txt.write(txt)
def OnResult(self,event):
txt = "Result received " + str(event.result)+"\n"
self.txt.write(txt)
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Main Frame', size=(600,400))
panel = MyPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()

Can't invoke button click function() outside pyQT based GUI

I am trying to enhance the existing pyQT based GUI framework. The GUI is a multithread GUI based out of pyQT. I have taken some portion of it here to explain the problem
GUI.py - main python script that invokes function to create Tab on GUI
from TabWidgetClass import ConstructGUITabs
class mainWindow(QtGui.QMainWindow):
def __init__(self,**kwargs):
super().__init__()
self.dockList = []
#Lets build&run GUI
self.BuildRunGUI() ### initialize GUI
def BuildRunGUI(self):
### frame, status definition
### tab definition
self.mainWidget = QtGui.QTabWidget(self)
self.setCentralWidget(self.mainWidget)
self.GUIConnect = ConstructGUITabs(docklist=self.dockList, parent_widget=self.mainWidget,mutex=self.mutex)
self.show()
if __name__ == "__main__":
try:
mW = mainWindow()
mW.setGeometry(20, 30, 1920*0.9,1080*0.9)
mW.setTabPosition(QtCore.Qt.AllDockWidgetAreas, QtGui.QTabWidget.North)
#mW.show()
TabWidgetClass.py - Script to create tabs & widgets
from SerialInterface import SerialInterface
from BackQThread import BackQThread
class ConstructGUITabs():
def __init__():
super().__init__() #init!!
#Thread Variables
self.main_thread = None
self.background_thread = None
# Let's define each tab here
self.ADD_CALIBRATE_TAB()
############## Start of ADD_CONNECT_TAB##################################################
def ADD_CALIBRATE_TAB(self, tec_layout):
#Create threads here. Main to handle update in GUI & Background thread for few specific
#time consuming operation
self.main_thread = SerialInterface()
self.background_thread = BackQThread()
#Get button connect to get_value through main thread
self.get_button = QtGui.QLabel(self)
self.set_button = QtGui.QPushButton('Set')
self.set_button.clicked.connect(self.set_value)
#Set button connect to Set_value through background_thread
self.get_button = QtGui.QLabel(self)
self.get_button = QtGui.QPushButton('Get')
self.get_button.clicked.connect(self.get_new_value)
self.pwr_disp_val = QtGui.QLineEdit(self)
self.pwr_disp_val.setText('')
#add the layout
#connect signal for backgrund thread
self.background_thread.read_info_flash.connect(self.update_info_from_flash)
def set_value(self):
self.main_thread.setvalue()
def get_new_value(self):
self.background_thread.call_update_info_bg_thread(flag = 'ReadSignalInfo',
list=[var1, var2])
def update_info_from_flash(self,flashData):
self.pwr_disp_val.setText(flashData)
The 2 library modules SerialInterface & BackQThread are as below:
SerialInterface.py
import pyftdi.serialext
class LaserSerialInterface(object):
def __init__():
self.port = pyftdi.serialext.serial_for_url('COM1',
baudrate=9600,
timeout=120,
bytesize=8,
stopbits=1,
parity='N',
xonxoff=False,
rtscts=False)
def setValue(self, value):
self.port.write(value)
def readvalue(self,value):#nITLA
return (value * 100)
BackQThread.py
from SerialInterface import SerialInterface
class BackQThread(QtCore.QThread):
signal = QtCore.pyqtSignal(object)
read_info_flash = QtCore.pyqtSignal(object)
def __init__(self):
self.serial = SerialInterface()
def call_update_info_bg_thread(self,flag,**kwargs): #nITLA
self.flag = flag
self.kwargs = kwargs
def update_info_bg_thread(self, Signallist): #nITLA
flashData = []
for index in range(0, len(Singnallist)):
flashData.append(self.serial.readvalue(Signallist[index]))
self.read_info_flash.emit(flashData)
def run(self):
while True:
time.sleep(0.1)
if self.flag == None:
pass
elif self.flag == 'ReadSignalInfo':
self.update_info_bg_thread(Signallist = self.kwargs['Signallist'])
self.flag = False
All above interface works without issues.
The new requirement is to do automation on GUI. In such way that, execute GUI options without manually doing clicks. Script should get an access to ConstructGUITabs() class, to invoke the click events.
Here is sample test script which works fine, if I invoke click events.
GUI.py - script is modified to declare GUIConnect as global variables using 'import globfile'
from TabWidgetClass import ConstructGUITabs
import globfile
class mainWindow(QtGui.QMainWindow):
def __init__(self,**kwargs):
super().__init__()
self.dockList = []
#Lets build&run GUI
self.BuildRunGUI() ### initialize GUI
def BuildRunGUI(self):
### frame, status definition
### tab definition
self.mainWidget = QtGui.QTabWidget(self)
self.setCentralWidget(self.mainWidget)
globfile.GUIConnect = ConstructGUITabs(docklist=self.dockList, parent_widget=self.mainWidget,mutex=self.mutex)
........
Test_Script.py
import globfile
globfile.GUIConnect.get_button.click() #this invokes get_new_value
globfile.GUIConnect.set_button.click()# this set_value
As you see here, through *click() function we are again doing mouse click events. I dont want to do this.
So i tried
globfile.GUIConnect.set_value()# this set_value
# This set_Value() would work as we are using only single & main thread
globfile.GUIConnect.get_new_value() #this invokes get_new_value
# This function call wont' get completed. It would stuck at 'self.read_info_flash.emit(flashData)' in BackQThread.py script. Hence, background thread fails to run update function 'update_info_from_flash'.
Can anyone tell me, what is the wrong in invoking directly functions rather than click() events. Why multithread alone fails to complete.

placing values on TextCtrl in subthread cause doesn't always work and cause random segmentation faults

I'm trying to create a gui application using wxpython and I got some issues with the TextCtrl element. The effect that I am trying to achieve is that the user will enter a command to a text field (command) and the command might pop a message that appears in the `(out) field. After some time (0.7 seconds on this example) the message will get back to a default message ("OutPut"). I have two problems:
The message doesn't always appear.
The program sometime crashes due to a segmentation fault and I don't get any Error message to handle that.
I guess that the two related in some way, but I don't know why. In the following example, I only type "test" and wait until the original message appear. Both problems happen on that scenario.
I post here two files that serve as smallest working example. File number 1, creates the GUI,
import wx
import os.path
import os
from threading import Thread
from time import sleep
from MsgEvent import *
class MainWindow(wx.Frame):
def __init__(self):
super(MainWindow, self).__init__(None, size=(400,200),)
#style=wx.MAXIMIZE)
self.CreateInteriorWindowComponents()
self.CreateKeyBinding()
self.command.SetFocus()
self.Layout()
def Test(self):
self.command.SetValue('open')
self.ParseCommand(None)
def PostMessage(self,msg):
'''For its some reason, this function is called twice,
the second time without any input. I could'nt understand why.
For that, the test :if msg == None'''
if msg == None: return
worker = MessageThread(self,msg,0.7,'OutPut')
worker.start()
def CreateKeyBinding(self):
self.command.Bind(wx.EVT_CHAR,self.KeyPressed)
def KeyPressed(self,event):
char = event.GetUniChar()
if char == 13 and not event.ControlDown(): #Enter
if wx.Window.FindFocus() == self.command:
self.ParseCommand(event)
else:
event.Skip()
def ParseCommand(self,event):
com = self.command.GetValue().lower() #The input in the command field
self.PostMessage(com)
def CreateInteriorWindowComponents(self):
''' Create "interior" window components. In this case it is just a
simple multiline text control. '''
self.panel = wx.Panel(self)
font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
font.SetPointSize(12)
self.vbox = wx.BoxSizer(wx.VERTICAL)
#Out put field
self.outBox = wx.BoxSizer(wx.HORIZONTAL)
self.out = wx.TextCtrl(self.panel, style=wx.TE_READONLY|wx.BORDER_NONE)
self.out.SetValue('OutPut')
self.out.SetFont(font)
self.outBox.Add(self.out,proportion=1,flag=wx.EXPAND,border=0)
self.vbox.Add(self.outBox,proportion=0,flag=wx.LEFT|wx.RIGHT|wx.EXPAND,border=0)
#Setting the backgroudn colour to window colour
self.out.SetBackgroundColour(self.GetBackgroundColour())
#Commands field
self.commandBox = wx.BoxSizer(wx.HORIZONTAL)
self.command = wx.TextCtrl(self.panel, style=wx.TE_PROCESS_ENTER)
self.command.SetFont(font)
self.commandBox.Add(self.command, proportion=1, flag=wx.EXPAND)
self.vbox.Add(self.commandBox, proportion=0, flag=wx.LEFT|wx.RIGHT|wx.EXPAND, border=0)
self.panel.SetSizer(self.vbox)
return
#Close the window
def OnExit(self, event):
self.Close() # Close the main window.
app = wx.App()
frame = MainWindow()
frame.Center()
frame.Show()
app.MainLoop()
And file number 2, called MsgThread.py handle the events.
import wx
import threading
import time
myEVT_MSG = wx.NewEventType()
EVT_MSG = wx.PyEventBinder(myEVT_MSG,1)
class MsgEvent(wx.PyCommandEvent):
""" event to signal that a message is ready """
def __init__(self,etype,eid,msg='',wait=0,msg0=''):
""" create the event object """
wx.PyCommandEvent.__init__(self,etype,eid)
self._msg = unicode(msg)
self._wait_time = wait
self._reset_message = unicode(msg0)
def GetValue(self):
""" return the value from the event """
return self._msg
class MessageThread(threading.Thread):
def __init__(self,parent,msg='',wait=0,msg0=''):
"""
parent - The gui object that shuold recive the value
value - value to handle
"""
threading.Thread.__init__(self)
if type(msg) == int:
msg = unicode(msg)
self._msg = msg
self._wait_time = wait
self._reset_message = msg0
self._parent = parent
print self._msg
def run(self):
""" overide thread.run Don't call this directly, its called internally when you call Thread.start()"""
self._parent.out.SetValue(unicode(self._msg))
time.sleep(self._wait_time)
self._parent.out.SetValue(self._reset_message)
self._parent.MessageFlag = False
event = MsgEvent(myEVT_MSG,-1,self._msg)
wx.PostEvent(self._parent,event)
What is faulty?
WX Python is not thread safe except for 3 functions (wx.CallAfter, wx.CallLater, wx.PostEvent)
So basically what you have to do is ensure you never call any widget directly from within the subthread. You may use events or CallAfter.
You also might want to check this:
Nice writeup about threads and wxpython
This might help as well
Lokla

wxpython - Running threads sequentially without blocking GUI

I've got a GUI script with all my wxPython code in it, and a separate testSequences module that has a bunch of tasks that I run based on input from the GUI. The tasks take a long time to complete (from 20 seconds to 3 minutes), so I want to thread them, otherwise the GUI locks up while they're running. I also need them to run one after another, since they all use the same hardware. (My rationale behind threading is simply to prevent the GUI from locking up.) I'd like to have a "Running" message (with varying number of periods after it, i.e. "Running", "Running.", "Running..", etc.) so the user knows that progress is occurring, even though it isn't visible. I'd like this script to run the test sequences in separate threads, but sequentially, so that the second thread won't be created and run until the first is complete. Since this is kind of the opposite of the purpose of threads, I can't really find any information on how to do this... Any help would be greatly appreciated.
Thanks in advance!
gui.py
import testSequences
from threading import Thread
#wxPython code for setting everything up here...
for j in range(5):
testThread = Thread(target=testSequences.test1)
testThread.start()
while testThread.isAlive():
#wait until the previous thread is complete
time.sleep(0.5)
i = (i+1) % 4
self.status.SetStatusText("Running"+'.'*i)
testSequences.py
import time
def test1():
for i in range(10):
print i
time.sleep(1)
(Obviously this isn't the actual test code, but the idea is the same.)
You cannot wait in the GUI-thread with a while loop because you block the processing of the event-queue. One solution is to poll the state of the thread with a timer:
import wx
import time
from threading import Thread
def test1():
for i in range(10):
print i
time.sleep(1)
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "Test")
panel = wx.Panel(self, -1)
sizer = wx.BoxSizer(wx.VERTICAL)
panel.SetSizer(sizer)
self.button = wx.Button(panel, 0, "Start")
sizer.Add(self.button, 0, wx.ALIGN_LEFT)
self.button.Bind(wx.EVT_BUTTON, self.OnButton)
self.text = wx.StaticText(panel, 0, "No test is running")
sizer.Add(self.text, 0, wx.ALIGN_LEFT)
self.timer = wx.Timer(self)
def OnButton(self, event):
self.testThread = Thread(target=test1)
self.testThread.start()
self.text.SetLabel("Running")
self.button.Disable()
self.Bind(wx.EVT_TIMER, self.PollThread)
self.timer.Start(20, oneShot=True)
event.Skip()
def PollThread(self, event):
if self.testThread.isAlive():
self.Bind(wx.EVT_TIMER, self.PollThread)
self.timer.Start(200, oneShot=True)
self.text.SetLabel(self.text.GetLabel() + ".")
else:
self.button.Enable()
self.text.SetLabel("Test completed")
app = wx.PySimpleApp()
TestFrame().Show()
app.MainLoop()
Figured out a way to do this. Instead of creating threads in my gui.py, I created a class that inherits from Thread, and runs all the tests in that class, then posts wxPython events when one test is done (so I can update the status bar) and when all tests are done (so I can inform the user that all tests are complete.
myEVT_TESTDONE = wx.NewEventType()
EVT_TESTDONE = wx.PyEventBinder(myEVT_TESTDONE , 1)
myEVT_ALLDONE = wx.NewEventType()
EVT_ALLDONE = wx.PyEventBinder(myEVT_ALLDONE, 1)
class TestDone(wx.PyCommandEvent):
def __init__(self, etype, eid, val=None):
wx.PyCommandEvent.__init__(self, etype, eid)
self._val = val
def GetValue(self):
return self._val
class AllDone(wx.PyCommandEvent):
def __init__(self, etype, eid):
wx.PyCommandEvent.__init__(self, etype, eid)
class TestSequence(Thread):
def __init__(self, parent, queue):
Thread.__init__(self)
self._queue = queue
self._parent = parent
self.start()
def run(self):
testCount = 0
for test in self._queue:
#Time-intensive task goes here
for i in range(10):
print i
sleep(1)
evt = TestDone(myEVT_TESTDONE, -1, i)
wx.PostEvent(self._parent, evt)
evt = AllDone(myEVT_ALLDONE, -1)
wx.PostEvent(self._parent, evt)
class MainSequence(wx.Frame):
def __init__(self, parent, id, title):
self.Bind(EVT_TESTDONE, self.testDoneEvt)
self.Bind(EVT_ALLDONE, self.allDoneEvt)
#...the rest of the wxPython code
def testDoneEvt(self, event):
#Set what to be done after every test, e.g. update progress bar
step = event.GetValue()
def allDoneEvt(self, event):
#Set what to be done after all tests, e.g. display "Tests complete"
program = wx.App()
window = MainSequence(None, -1, 'App title')
program.MainLoop()

Categories

Resources