I'm following the example given in http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/
I have a thread which is checking an sftp server for new files every 30 seconds. If it finds files, it uploads them to a db, and then it should trigger an update of certain GUI elements which will reload from the db.
The custom event code:
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
The ftp thread:
class FTPThread(threading.Thread):
def __init__(self,wxObject):
"""Init Worker Thread Class."""
threading.Thread.__init__(self)
self.wxObject = wxObject
self._stop = threading.Event()
self._stop.set()
self.start() # start the thread
def run(self):
while True:
time.sleep(30)
if not self._stop.isSet():
wx.CallAfter(self.parseFTP)
def stop(self):
self._stop.set()
def resume(self):
self._stop.clear()
def parseFTP(self):
#connect to db
...
#connect to sftp site
...
files_found=False
#process each file and delete
for file in dirlist:
files_found=True
...#process into db
sftp.remove(file)
sftp.close()
t.close()
#trigger update event if files found
if files_found==True:
wx.PostEvent(self.wxObject, ResultEvent("Files found"))
One of the GUI elements:
class MyGrid(wx.grid.Grid):
def __init__(self, parent):
wx.grid.Grid.__init__(self, parent,-1,style=wx.EXPAND)
self.parent=parent
...
self.update()
EVT_RESULT(self, self.updateFromEvent)
def updateFromEvent(self,event):
self.update()
def update(self):
...
Following debugging, the wx.PostEvent is being created, but not triggering any response in the grid.
The only difference I can find between the example and my code is that in the example the EVT_RESULT is in the main Frame, and not a GUI element - is this required?
Events don't propagate to its children so if MyGrid is a child of your main frame, events posted in the main won't make it through to MyGrid. What you can do instead is bind the event handler directly to your function within the instance of MyGrid like so:
"""from MainWindow"""
self._workerthread = FtpThread(...)
self._mygrid = MyGrid(...)
# Bind event
EVT_RESULT(self, self._mygrid.updateFromEvent)
I'm not too familiar with this kind of binding as I typically use wx.Bind.
I'm not sure, but that example was based on something in the wiki: http://wiki.wxpython.org/LongRunningTasks
I suspect that since it says "win" as an argument, it is probably referring to a Top Level Window, so the wx.Frame is probably required. You can still update the grid from the frame though.
EDIT: Manny has a good point. That would probably work too. And pubsub rocks!
Related
I would like to divide my GUI into processes that are run when activated by a ProcessManager.
As the diagram below shows, the MainWindow would instantiate the ProcessManager, which instantiates the Processes.
Now let's say I want to interact with components of the GUI across threads (using signals and slots) -- as an example (also in the picture below):
Process2 has a method that reads out the QLineEdit foo_line_edit.text() and enters it to the QLabel via foo_label.setText().
If I have many processes, I would prefer if all the logic would be implement on the Process without having to add to much on the MainWindow, therefore:
Is there a way to define such a process without having to create any methods/slots in the MainWindow? If there isn't how can I create a process driven design with Qt?
Here is the simplest implementation that comes to my mind:
class Main(QMainWindow):
foo_line_edit = QLineEdit()
foo_label = QLabel()
def __init__(self):
self.process_manager = ProcessManager(self)
# I would like to have the logic of this line in the Process2 class
# (Not sure, but I think it needs to be defined in the main thread?)
self.process_manager.process2.change_label.connect(self.process2_specific_change_label)
# This is the functionality, I would like to define in the Process2 class
def process2_specific_change_label(text):
self.foo_label.setText(text)
class ProcessManager(QObject):
def __init__(self, main_gui):
self.main_gui = main_gui
self.process2 = Process2(self)
class Process(QObject):
def __init__(self, process_manager):
self.process_manager=process_manager
self.thread = QThread()
self.moveToThread(self.thread)
self.thread.start()
class Process2(Process):
change_label = pyqtSignal(str)
def run(self):
# I think this line works, although it already creates access across threads, right?
text = self.process_manager.main_gui.foo_line_edit.text()
# This line does not work because of threads:
self.process_manager.main_gui.foo_label.setText(text)
# This works, but requires the function to be defined in the main thread?
change_label.emit(text)
Rethinking my original requirement and processing musicamante's input, it came to my mind, that the Process Manager and the Processes should run directly in the main thread, calling processspecific workers in threads. This way, the logic is within the specific Process and not somewhere in the MainWindow.
I agree, that the word process is confusing here. It was not meant in a programmatical aspect, but in its application aspect (the gui will follow along a production process). Renaming Process to Step might be better.
This leaves us with the following architecture:
The signals and slots are defined in the Step and its Worker counterpart.
class Main(QMainWindow):
foo_line_edit = QLineEdit()
foo_label = QLabel()
def __init__(self):
self.step_manager = StepManager(self)
class StepManager(QObject):
def __init__(self, main_gui):
self.main_gui = main_gui
self.step2 = Step2(self)
# For example simplicity, the step is run directly:
self.step2.run()
class Step(QObject):
start_worker = pyqtSignal()
worker = Worker()
def __init__(self, step_manager):
self.step_manager=step_manager
self.start_worker.connect(self.worker.run)
def run(self):
self.start_worker.emit()
class Worker(QObject):
finished = pyqtSignal()
def __init__(self):
self.thread = QThread()
self.moveToThread(self.thread)
self.thread.start()
def run(self):
self.finished.emit()
class Step2(Step):
start_worker = pyqtSignal(str)
worker = Worker2()
def __init__(self, step):
super().__init__(step)
self.worker.finished.connect(self.change_label)
def run(self):
text = self.step_manager.main_gui.foo_line_edit.text()
self.start_worker.emit(text)
def change_label(self, text):
self.step_manager.main_gui.foo_label.setText(text)
class Worker2(Worker):
finished = pyqtSignal(str)
def run(self, text):
self.finished.emit(text)
Although 2 classes need to be managed for each step, the MainWindow is free from step specific logic.
Of course, I'm very open to suggestions.
I am writing a Python 3.9 GUI application using wxPython. The application allows the user to enter information and press a "calculate" button. This will kick of a long running thread that updates the main application window with status updates. The application successfully kicks off the calculation thread and remains responsive.
Now I would like to add an "abort" button that will stop/cancel/abort the previously started thread when the users chooses to press the "abort" button.
After looking for some time, I found the code below on the wxPython website that has the general framework I am looking for. It follows the main logic of my application in a more simple form. It is missing the ongoing GUI status update, so I made a single addition to simulate the GUI status updates - see "<<<<<<" reference in the code below.
wx.PostEvent(self._notify_window, ResultEvent(i))
However, after adding this line it seems to break the event handling when the user is pressing the "stop" button while the processing thread has started. When pressing the "stop" button after 3-4 seconds, the counting thread in the sample code below continues to run and is ignoring the "stop" button event.
When this line is removed, the "stop" feature in the example code below works and the loop is disrupted.
How can I keep the feature of updating the GUI with status updates while the counting thread is running and also have the "stop" button terminate the actively running thread?
I am sure there are ways to optimize the code below. Please don't hesitate to share your thoughts/knowledge. Thank you.
import time
from threading import *
import wx
# Button definitions
ID_START = wx.NewId()
ID_STOP = wx.NewId()
# Define notification event for thread completion
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
# 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._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
for i in range(10):
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
time.sleep(2)
wx.PostEvent(self._notify_window, ResultEvent(i)) <<<<<<< Added to simulate "status update" feedback of my application.
# 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
# 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, 'Thread Test')
# Dumb sample frame with two buttons
wx.Button(self, ID_START, 'Start', pos=(0,0))
wx.Button(self, ID_STOP, 'Stop', pos=(0,50))
self.status = wx.StaticText(self, -1, '', pos=(0,100))
self.Bind(wx.EVT_BUTTON, self.OnStart, id=ID_START)
self.Bind(wx.EVT_BUTTON, self.OnStop, id=ID_STOP)
# Set up event handler for any worker thread results
EVT_RESULT(self,self.OnResult)
# And indicate we don't have a worker thread yet
self.worker = None
def OnStart(self, event):
"""Start Computation."""
# Trigger the worker thread unless it's already busy
if not self.worker:
self.status.SetLabel('Starting computation')
self.worker = WorkerThread(self)
self.worker.start()
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
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
if __name__ == '__main__':
app = MainApp(0)
app.MainLoop()
You are simply setting self.worker = None at the wrong point.
It should only be set to None on Stop or when it terminates naturally, other than that the code is fine.
e.g.
import time
from threading import *
import wx
# Button definitions
ID_START = wx.NewId()
ID_STOP = wx.NewId()
# Define notification event for thread completion
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
# 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._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
for i in range(10):
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
time.sleep(1)
wx.PostEvent(self._notify_window, ResultEvent(i)) # <<<<<<< Added to simulate "status update" feedback of my application.
# 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)
time.sleep(2)
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
# 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, 'Thread Test')
# Dumb sample frame with two buttons
wx.Button(self, ID_START, 'Start', pos=(0,0))
wx.Button(self, ID_STOP, 'Stop', pos=(0,50))
self.status = wx.StaticText(self, -1, '', pos=(0,100))
self.Bind(wx.EVT_BUTTON, self.OnStart, id=ID_START)
self.Bind(wx.EVT_BUTTON, self.OnStop, id=ID_STOP)
# Set up event handler for any worker thread results
EVT_RESULT(self,self.OnResult)
# And indicate we don't have a worker thread yet
self.worker = None
def OnStart(self, event):
"""Start Computation."""
# Trigger the worker thread unless it's already busy
if self.worker:
return
else:
self.status.SetLabel('Starting computation')
self.worker = WorkerThread(self)
self.worker.start()
self.status.SetLabel('Computation started')
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()
self.worker = None
else:
self.status.SetLabel('Computation not running')
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)
if event.data == 10:
self.worker = None
self.status.SetLabel('Computation Finished')
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
if __name__ == '__main__':
app = MainApp(0)
app.MainLoop()
I have been making a GUI for a genetic algorithm I am working on and I made the mistake of leaving the threading so late simply because I did not (and still don't) know how to do it. So essentially when the start button is clicked the function 'run' starts the whole infinite loop process which actually happens in generation_loop. Each generation the loop checks to see if it should still be running. The idea is that if the stop or pause button has been clicked it will stop looping (with the stop button all the data is cleared with the pause button it remains and the unpause button just sets running to True and calls generation_loop)
So I need to work out a way to make my GUI responsive while generation_loop is running. Here is my code, I tried to minimise it but I am unsure what is important information for threading:
class Window(main_window, QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
main_window.__init__(self)
self.setupUi(self)
self.scene = QGraphicsScene()
self.im_view.setScene(self.scene)
self.setWindowTitle('Fantasy Generator')
self.running = False
self.first_run = True
self.im = Image.new('RGBA', (400, 400), (0, 0, 0, 255))
self.saved_gens = deque([('A', self.im, self.im, self.im)])
self.set_save_amount(self.sb_saveamt.value())
self.population = []
self.btn_exit.clicked.connect(self.close)
self.actionQuit.triggered.connect(self.close)
self.btn_pauser.clicked.connect(self.pause_button)
self.sb_saveamt.valueChanged[int].connect(self.set_save_amount)
self.btn_restart.clicked.connect(self.start_button)
self.btn_loadimage.clicked.connect(self.get_image)
self.actionLoad_Image.triggered.connect(self.get_image)
self.gen_sldr.valueChanged[int].connect(self.display_gen)
self.cb_display.currentIndexChanged.connect(self.change_quality)
self.has_image = True
self.display_gen(0)
def get_image(self):
pass
# To save you time I removed the code here. It just sets self.im using a file dialog basically
def set_save_amount(self, amt):
if amt == -1:
self.saved_gens = deque(self.saved_gens)
else:
self.saved_gens = deque(self.saved_gens, amt + 1)
def pause_button(self):
if self.first_run:
self.run()
elif self.running:
self.running = False
self.btn_pauser.setText('Resume Execution')
# pause stuff goes here
else:
self.running = True
self.btn_pauser.setText('Pause Execution')
self.generation_loop()
# resume from pause stuff goes here
def start_button(self):
if self.first_run:
self.run()
else:
self.end()
# The run function should start the actual process
def run(self):
self.btn_restart.setText('End')
self.btn_pauser.setText('Pause Execution')
self.first_run = False
self.running = True
settings = dict(ind_per_gen=self.sb_ipg.value(), shapes_per_im=self.sb_spi.value(),
complexity=self.sb_complexity.value(), mut_rate=self.sb_mutation.value(),
cross_chance=self.sb_cross.value(), seed=self.sb_seed.value())
self.population = Population(self.im, **settings)
self.generation_loop()
# This is the loop I want to be able to exit out of using buttons
def generation_loop(self):
while self.running:
if self.first_run:
break
self.add_generation_data(self.population.next_gen())
def end(self):
self.btn_restart.setText('Start')
self.btn_pauser.setText('Start Execution')
self.first_run = True
self.running = False
self.saved_gens = deque([('A', self.im, self.im, self.im)])
self.set_save_amount()
self.display_gen(0)
def add_generation_data(self, data):
self.saved_gens.append(data)
self.gen_sldr.setMaximum(len(self.saved_gens) - 1)
self.gen_sldr.setValue(len(self.saved_gens) - 1)
self.display_gen(data[0] + 1)
def change_quality(self):
self.display_gen(self.gen_sldr.value())
def resizeEvent(self, e):
if self.has_image:
self.im_view.fitInView(QRectF(0, 0, self.width, self.height), Qt.KeepAspectRatio)
self.scene.update()
def display_image(self, image):
self.scene.clear()
if image.mode != 'RGBA':
image = image.convert('RGBA')
self.width, self.height = image.size
qim = ImageQt.ImageQt(image)
pixmap = QPixmap.fromImage(qim)
self.scene.addPixmap(pixmap)
self.im_view.fitInView(QRectF(0, 0, self.width, self.height), Qt.KeepAspectRatio)
self.scene.update()
def display_gen(self, index):
self.lcd_cur_gen.display(self.saved_gens[index][0])
if self.cb_display.currentIndex() == 0:
self.display_image(self.saved_gens[index][1])
elif self.cb_display.currentIndex() == 1:
self.display_image(self.saved_gens[index][2])
else:
self.display_image(self.saved_gens[index][3])
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
EDIT: I also just found at that I can't even change the graphics view from within the generation_loop but it works and changes if I limit the loop
In order to move your long running code to a thread, you need to first identify which parts of the long running code interact with the GUI and which parts don't. The key reason for this is that interacting with the GUI from a secondary thread is forbidden, and will lead to segfaults.
It looks like self.population.next_gen() is the long running bit of the code and doesn't interact with the GUI (although what this does is not provided so I can't be sure) while self.add_generation_data(...) updates the GUI which should be reasonably fast.
As such, this makes it reasonably simple to separate, which I'll show below.
Now, about threads. Python provides threads through the threading module (as the other answers show), however these not are recommended for use with a PyQt application if you want your thread to have any relation to the GUI (see here). PyQt also provides threading via the QThread object, which integrates support for sending and receiving Qt signals (which are thread safe). In short, the QThread has a separate event loop, and processes signals received asynchronously to the main thread, thus leaving the event loop in the main thread to process GUI events (like button clicks).
Typically you create a new class that inherits from QObject, instantiate it and move it to a QThread. Slots (aka methods) in the object that are triggered by a signal emission, then run in the thread.
So you'll want to do something like this
class MyWorker(QObject):
done = pyqtSignal(object) # you may need to update "object" to the type returned by Population.next_gen()
def __init__(self, settings):
# create the population object with whatever settings you need
# Note that this method runs in the parent thread as you have
# yet to move the object to a new thread. It shouldn't cause any
# problems, but may depend on what the Population class is/does.
# TODO: I've removed the reference to an image here...
#it may or may not be thread safe. I can't tell from your code.
self.population = Population(..., settings)
#pyqtSlot()
def next_gen(self):
new_gen = self.population.next_gen()
self.done.emit(new_gen)
class Window(....):
make_next_generation = pyqtSignal()
....
def run(self):
self.btn_restart.setText('End')
self.btn_pauser.setText('Pause Execution')
self.first_run = False
self.running = True
settings = dict(ind_per_gen=self.sb_ipg.value(), shapes_per_im=self.sb_spi.value(),
complexity=self.sb_complexity.value(), mut_rate=self.sb_mutation.value(),
cross_chance=self.sb_cross.value(), seed=self.sb_seed.value())
self.setupThread(settings)
def setupThread(self, settings):
self.thread = QThread()
self.worker = MyWorker(settings)
self.worker.moveToThread(self.thread)
# connect a signal in the main thread, to a slot in the worker.
# whenever you emit the signal, a new generation will be generated
# in the worker thread
self.make_next_generation.connect(self.worker.next_gen)
# connect the signal from the worker, to a slot in the main thread.
# This allows you to update the GUI when a generation has been made
self.worker.done.connect(self.process_generation)
# Start thread
self.thread.start()
# emit the signal to start the process!
self.make_next_generation.emit()
def process_generation(new_gen):
# run the GUI side of the code
# ignore the new generation if the "end" button was clicked
if not self.first_run:
self.add_generation_data(new_gen)
if self.running:
# make another generation in the thread!
self.make_next_generation.emit()
def pause_button(self):
if self.first_run:
self.run()
elif self.running:
self.running = False
self.btn_pauser.setText('Resume Execution')
# pause stuff goes here
else:
self.running = True
self.btn_pauser.setText('Pause Execution')
# make another generation in the thread!
self.make_next_generation.emit()
Things to note:
I haven't included all of your code in my answer. Merge as appropriate.
I'm unsure what self.im is. It's passed to Population so there might be some thread unsafe behaviour in your code that I can't see. I've left it to you to fix
I'm familiar with PyQt4, not PyQt5, so there is a possibility some things I've done don't work quite right. It should be easy for you to work out what to change from any error messages that are raised.
It's a bit messy recreating the thread and worker each time it is started from scratch. You might want to consider moving the instantiation of Population to a method in the worker (one that isn't __init__ and invoking it each time you want to start from scratch (in the same way we trigger a new generation). This would allow you to move pretty much all of setupThread to the Window.__init__ method and then when the start button was clicked, you'd just emit a signal to recreate Population followed by one to make the first generation.
You can use Threading events here.
from threading import Thread, Event
Once you detect the button click,
class MyThread(Thread):
def __init__(self, the_function, <any input param you want to provide>):
Thread.__init__(self)
self.stop_event = Event()
self.exec_func = the_function
def set_stop_flag(self, value):
if value:
self.stop_event.set()
else:
self.stop_event.clear()
def run(self):
while True:
try:
if not self.stop_event.is_set()
self.exec_func()
else:
break # once the event is set, you can break which will kill this thread.
# To stop busy waiting you can make this thread sleep for some seconds after each cycle.
import time
time.sleep(10) # 10 seconds wait before the next cycle.
except Exception, excep:
print "Got exception > ", str(excep)
Now in your code you embed this code piece and keep a reference for this thread.
Let's say
self.my_thread = MyThread(self.function_to_perform, <blabla>)
self.my_thread.setDaemon(True) # So that you don't have to worry about it when the Main process dies!
self.my_thread.start()
Now once you get a STOP button click event you call
self.my_thread.set_stop_flag(True) # Bingo! Your thread shall quit.
I am taking photos using gphoto2 and would like to them to a list widget asynchronously as the photos are taken but for some reason it isn't working as intended. It takes the photo on a QThread but is not adding the photo to the list until all photos have been taken (like a bulk add). How would I go about this?
Here is the relevant source code (it won't compile because as there is too many dependencies to fit within the question):
class DownloadThread(QThread):
data_downloaded = Signal(object)
def __init__(self, photo_name):
QThread.__init__(self)
self.photo_name = photo_name
def run(self):
image_location = capture_image.take_photo(self.photo_name)
image = QImage(image_location)
to_pixmap = QPixmap.fromImage(image).scaled(200, 200)
to_qicon = QIcon(to_pixmap)
self.data_downloaded.emit(QListWidgetItem(to_qicon, image_location))
class MainWindow(QMainWindow, Ui_MainWindow):
def take_photo(self):
import time
for x in range(2):
photo_name = str(x) +'.jpg'
downloader = DownloadThread(photo_name)
downloader.data_downloaded.connect(self.on_photo_ready)
downloader.start()
time.sleep(5)
def on_photo_ready(self, photo):
print "WHY"
self.listWidget.addItem(photo)
I have some simple print statement in the function being called so the terminal looks like this:
Photo
Photo
Photo
Photo
Photo
Photo
WHY
WHY
WHY
WHY
WHY
WHY
Meaning it waits to actually call emit until the for loop is complete and not on its own thread as intended. Any help would be AWESOME!
You are telling the main thread to sleep while the secondary thread works. This queues all of your signals so that they arrive at once. Remove time.sleep(5) and change
downloader = ...
To
self.downloader = ...
And you should be fine.
That said, the worker model is a Good Thing. See this question or this one for details.
There are a few problems
1. Your thread isn't actually running
You need to call QThread.start() to actually run QThread.run(). That being said, you probably don't want to design your application like this. There's no reason to create dozens or hundreds of different threads -- one for each image download. It would be far more efficient to create one worker thread that downloads all the images in a queue. See below for an example.
2. You can't create QPixmaps or GUI items in a secondary thread
You can't create QPixmap's outside the main thread. You can't create QListWidgetItem's either, or any GUI element for that matter; they can only be created (and safely manipulated) in the main thread. You can use other similar elements (like QImage), but really, the only thing you need to pass back to the main thread is the downloaded filepath; the main thread can handle the QPixmap and item creation.
class DownloadWorker(QObject):
data_downloaded = Signal(object)
#QtCore.Slot(str)
def download_image(self, name):
image_location = capture_image.take_photo(name)
self.data_downloaded.emit(image_location)
class MainWindow(QMainWindow, Ui_MainWindow):
request_download = QtCore.Signal(str)
def __init__(self, ...)
...
self.worker = DownloadWorker()
self.thread = QThread(self)
self.request_download.connect(self.worker.download_image)
self.worker.data_downloaded.connect(self.on_photo_ready)
self.worker.moveToThread(self.thread)
self.thread.start()
self.timer = QTimer(self)
self.timer.timeout.connect(self.take_photo)
self.timer.start(5000)
def take_photo(self):
import time
photo_name = str(time.time()) +'.jpg'
self.request_download.emit(photo_name)
#QtCore.Slot(str)
def on_photo_ready(self, filepath):
item = QListWidgetItem(QIcon(filepath))
self.listWidget.addItem(item)
I'm new to python and in my project I have completed implementing a function which has implementation to get data from a micro-controller (sampling data over UART to my PC).
Which takes several seconds, it depends on how many samples I wish to collect. This works fine with straightforward python script.
However, I wish to implement a GUI and I chose PyQt to do it. All I want to do is call the function when a button is pressed.
I will try to explain what I want to achieve in sequential steps below:
Click the button.
Button is disabled.
Call the function collectDataFromUART().
Wait for/detect the data collection to complete (Several Seconds)
Re-enable the button.
I have a button clicked handler, as shown below:
self.ui.pushButton1.clicked.connect(self.handlepushButton1)
def handlepushButton1(self):
self.ui.textEdit1.append("Started")
collectDataFromUART()
What I'm not able to understand is how to detect the completion of the function collectDataFromUART() and only then re-enable the button.
Can anyone throw light on this? Examples will be really very helpful.
Help! Thank you.
I suggest put collectDataFromUART() in another thread and emit message when it done. Something like this:
mysignal = QtCore.pyqtSignal()
class NamedThread(QtCore.QThread):
def __init__(self, parent=None):
super(self.__class__, self).__init__(parent)
def run(self):
<some code from collectDataUART>
self.mysignal.emit()
class NamedWidget(QtGui.QWidget):
def __init__(self, parent=None):
<some code>
self.thread = NamedThread()
self.pushButton1.clicked.connect(self.handlepushButton1)
self.thread.mysignal.connect(lambda: self.pushButton1.setEnabled(True), QtCore.Qt.QueuedConnection)
def handlepushButton1(self):
self.pushButton1.setDisabled(True)
self.thread.start()
You also can add some information about status of execution in signal. To do so you need pyqtSiglnal([type of data you want to send]) after that just call emit with some data self.mysignal[type of data].emit(<data>)
For my opinion, sound like your should handle it by create QThread to receive your UART data. First, your have initiate thread for receive your UART data and close button. Next, wait for this thread. If success or fail, Create signal send back to main widget. Last, Handle signal data it and do want your want;
Little example;
import sys
import time
from PyQt4 import QtGui
from PyQt4 import QtCore
def collectDataFromUART ():
# Your collect data from UART
time.sleep(1)
data = 'UART'
return data
class QCustomThread (QtCore.QThread):
status = QtCore.pyqtSignal(object, object)
def __init__ (self, parentQWidget = None):
super(QCustomThread, self).__init__(parentQWidget)
def run (self):
try:
data = collectDataFromUART()
errorCode = None
except Exception, error:
data = None
errorCode = str(error)
self.status.emit(data, errorCode)
self.exit(0)
class QCustomMainWindow (QtGui.QMainWindow):
def __init__ (self):
super(QCustomMainWindow, self).__init__()
self.startQPushButton = QtGui.QPushButton('START')
self.startQPushButton.released.connect(self.requestWork)
self.setCentralWidget(self.startQPushButton)
def requestWork (self):
self.startQPushButton.setEnabled(False)
myQCustomThread = QCustomThread(self)
myQCustomThread.status.connect(self.relpyWork)
myQCustomThread.start()
def relpyWork (self, data, errorCode):
self.startQPushButton.setEnabled(True)
if errorCode == None:
QtGui.QMessageBox.information(self, 'Information', data)
else:
QtGui.QMessageBox.critical(self, 'Critical', errorCode)
myQApplication = QtGui.QApplication(sys.argv)
myQCustomMainWindow = QCustomMainWindow()
myQCustomMainWindow.show()
sys.exit(myQApplication.exec_())