Let's say I have a PyQt program that goes through a given directory, looks for *JPEG images, and does some processing every time it finds one. Depending on the size of the selected directory, this may take from some seconds to minutes.
I would like to keep my user updated with the status - preferably with something like "x files processed out of y files" . If not, a simple running pulse progress bar by setting progressbar.setRange(0,0) works too.
From my understanding, in order to prevent my GUI from freezing, I will need a seperate thread that process the images, and the original thread that updates the GUI every interval.
But I am wondering if there is any possible way for me to do both in the same thread?
Yes, you can easily do this using processEvents, which is provided for this exact purpose.
I have used this technique for implementing a simple find-in-files dialog box. All you need to do is launch the function that processes the files with a single-shot timer, and then periodically call processEvents in the loop. This is should be good enough to update a counter with the number of files processed, and also allow the user to cancel the process, if necessary.
The only real issue is deciding on how frequently to call processEvents. The more often you call it, the more responsive the GUI will be - but this comes at the cost of considerably slowing the processing of the files. So you may have to experiment a little bit in order to find an acceptable compromise.
UPDATE:
Here's a simple demo that shows how the code could be structured:
import sys, time
from PyQt5 import QtWidgets, QtCore
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.button = QtWidgets.QPushButton('Start')
self.progress = QtWidgets.QLabel('0')
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
layout.addWidget(self.progress)
self.button.clicked.connect(self.test)
self._stop = False
self._stopped = True
def test(self):
if self._stopped:
self._stop = False
self.progress.setText('0')
self.button.setText('Stop')
QtCore.QTimer.singleShot(1, self.process)
else:
self._stop = True
def process(self):
self._stopped = False
for index in range(1, 1000):
time.sleep(0.01)
self.progress.setText(str(index))
if not index % 20:
QtWidgets.qApp.processEvents(
QtCore.QEventLoop.AllEvents, 50)
if self._stop:
break
self._stopped = True
self.button.setText('Start')
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
I could not achieve the thing you need without multi threading and this is not possible because gui can be only updated in main thread. Below is an algorithm how I did this with multithreading.
Let's say you have your application processing images. Then there are the following threads:
Main thread (that blocks by GUI/QApplication-derived classes.exec())
Timer with, for example, 1 second interval which updates a variable and calls a slot in GUI thread which updates a variable in user interface.
A thread which is processing images on your pc.
def process(self):
self._status = "processing image 1"
....
def _update(self):
self.status_label.setText(self._status)
def start_processing(self, image_path):
# create thread for process and run it
# create thread for updating by using QtCore.QTimer()
# connect qtimer triggered signal to and `self._update()` slot
# connect image processing thread (use connect signal to any slot, in this example I'll stop timer after processing thread finishes)
#pyqtSlot()
def _stop_timer():
self._qtimer.stop()
self._qtimer = None
_update_thread.finished.connect(_stop_timer)
In pyqt5 it is possible to assign a pyqtvariable from a one nested thread(first level). So you can make your variable a pyqtvariable with setter and getter and update gui in a setter or think how you can do this by yourself.
You could just use the python threading module and emit a signal in your threaded routine.
Here's a working example
from PyQt4 import QtGui, QtCore
import threading
import time
class MyWidget(QtGui.QWidget):
valueChanged = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
self.computeButton = QtGui.QPushButton("Compute", self)
self.progressBar = QtGui.QProgressBar()
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.computeButton)
layout.addWidget(self.progressBar)
self.computeButton.clicked.connect(self.compute)
self.valueChanged.connect(self.progressBar.setValue)
def compute(self):
nbFiles = 10
self.progressBar.setRange(0, nbFiles)
def inner():
for i in range(1, nbFiles+1):
time.sleep(0.5) # Process Image
self.valueChanged.emit(i) # Notify progress
self.thread = threading.Thread(target = inner)
self.thread.start()
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())
Related
I have a UI that I am wanting to use threading with inside of Maya. The reason for doing this is so I can run Maya.cmds without hanging/freezing the UI while updating the UI with progress bars, etc.
I have read a few examples from StackOverflow but my code is crashing every second time I run it. Examples I have followed are here and here
import maya.cmds as cmds
from PySide2 import QtWidgets, QtCore, QtGui, QtUiTools
import mainWindow #Main window just grabs the Maya main window and returns the object to use as parent.
class Tool(QtWidgets.QMainWindow):
def __init__(self, parent=mainWindow.getMayaMainWindow()):
super(Tool, self).__init__(parent)
UI = "pathToUI/UI.ui"
loader = QtUiTools.QUiLoader()
ui_file = QtCore.QFile(UI)
ui_file.open(QtCore.QFile.ReadOnly)
self.ui = loader.load(ui_file, self)
#Scans all window objects and if one is open with the same name as this tool then close it so we don't have two open.
mainWindow.closeUI("Tool")
###HERE'S WHERE THE THREADING STARTS###
#Create a thread
thread = QtCore.QThread()
#Create worker object
self.worker = Worker()
#Move worker object into thread (This creates an automatic queue if multiples of the same worker are called)
self.worker.moveToThread(thread)
#Connect buttons in the UI to trigger a method inside the worker which should run in a thread
self.ui.first_btn.clicked.connect(self.worker.do_something)
self.ui.second_btn.clicked.connect(self.worker.do_something_else)
self.ui.third_btn.clicked.connect(self.worker.and_so_fourth)
#Start the thread
thread.start()
#Show UI
self.ui.show()
class Worker(QtCore.QObject):
def __init__(self):
super(Worker, self).__init__() #This will immediately crash Maya on tool launch
#super(Worker).__init__() #This works to open the window but still gets an error '# TypeError: super() takes at least 1 argument (0 given)'
def do_something(self):
#Start long code here and update progress bar as needed in a still active UI.
myTool.ui.progressBar.setValue(0)
print "doing something!"
myTool.ui.progressBar.setValue(100)
def do_something_else(self):
#Start long code here and update progress bar as needed in a still active UI.
myTool.ui.progressBar.setValue(0)
print "doing something else!"
myTool.ui.progressBar.setValue(100)
def and_so_fourth(self):
#Start long code here and update progress bar as needed in a still active UI.
myTool.ui.progressBar.setValue(0)
print "and so fourth, all in the new thread in a queue of which method was called first!"
myTool.ui.progressBar.setValue(100)
#A Button inside Maya will import this code and run the 'launch' function to setup the tool
def launch():
global myTool
myTool = Tool()
I'm expecting the UI to stay active (not locked up) and the threads to be running Maya cmds without crashing Maya entirely while updating the UIs progress bars.
Any insight on this would be amazing!
From what I see it has the following errors:
thread is a local variable that is deleted when the constructor is finished executing causing what is executed to be done in the main thread which is not desired, the solution is to extend the life cycle and for this there are several solutions: 1) make class attribute, 2) pass a parent to the cycle of life they are managed by the parent. In this case use the second solution.
You should not modify the GUI from another thread, in your case you have modified the progressBar from another thread, in Qt you must use signals.
You must use the #Slot decorator in the methods that are executed in another thread.
You indicate that you want to modify myTool but you have not declared it, soglobal myTool will not work by making myTool a local variable to be deleted. The solution is to declare myTool:myTool = None
Considering the above, the solution is:
import maya.cmds as cmds
from PySide2 import QtWidgets, QtCore, QtGui, QtUiTools
import mainWindow # Main window just grabs the Maya main window and returns the object to use as parent.
class Tool(QtWidgets.QMainWindow):
def __init__(self, parent=mainWindow.getMayaMainWindow()):
super(Tool, self).__init__(parent)
UI = "pathToUI/UI.ui"
loader = QtUiTools.QUiLoader()
ui_file = QtCore.QFile(UI)
ui_file.open(QtCore.QFile.ReadOnly)
self.ui = loader.load(ui_file, self)
# Scans all window objects and if one is open with the same name as this tool then close it so we don't have two open.
mainWindow.closeUI("Tool")
# Create a thread
thread = QtCore.QThread(self)
# Create worker object
self.worker = Worker()
# Move worker object into thread (This creates an automatic queue if multiples of the same worker are called)
self.worker.moveToThread(thread)
# Connect buttons in the UI to trigger a method inside the worker which should run in a thread
self.ui.first_btn.clicked.connect(self.worker.do_something)
self.ui.second_btn.clicked.connect(self.worker.do_something_else)
self.ui.third_btn.clicked.connect(self.worker.and_so_fourth)
self.worker.valueChanged.connect(self.ui.progressBar.setValue)
# Start the thread
thread.start()
# Show UI
self.ui.show()
class Worker(QtCore.QObject):
valueChanged = QtCore.Signal(int)
#QtCore.Slot()
def do_something(self):
# Start long code here and update progress bar as needed in a still active UI.
self.valueChanged.emit(0)
print "doing something!"
self.valueChanged.emit(100)
#QtCore.Slot()
def do_something_else(self):
# Start long code here and update progress bar as needed in a still active UI.
self.valueChanged.emit(0)
print "doing something else!"
self.valueChanged.emit(100)
#QtCore.Slot()
def and_so_fourth(self):
# Start long code here and update progress bar as needed in a still active UI.
self.valueChanged.emit(0)
print "and so fourth, all in the new thread in a queue of which method was called first!"
self.valueChanged.emit(100)
myTool = None
def launch():
global myTool
myTool = Tool()
Please bear with my question as I am a beginner. I have been having problems implementing the progress bar in pyqt and all of the example I have seen doesn't really explain on how to implement it properly and from this example and this example I somewhat partially made it work but it still hangs. I have this code:
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(750, 450, 400, 200)
self.setFixedSize(self.size())
btn1 = QtGui.QPushButton("Convert", self)
btn1.move(210,171)
btn1.clicked.connect(self.progbar)
def progbar (self):
self.prog_win = QDialog()
self.prog_win.resize(400, 100)
self.prog_win.setFixedSize(self.prog_win.size())
self.prog_win.setWindowTitle("Processing request")
self.lbl = QLabel(self.prog_win)
self.lbl.setText("Please Wait. . .")
self.lbl.move(15,18)
self.progressBar = QtGui.QProgressBar(self.prog_win)
self.progressBar.resize(410, 25)
self.progressBar.move(15, 40)
self.progressBar.setRange(0,1)
self.myLongTask = TaskThread()
#I think this is where I am wrong
#because all of the answers here is very specific
#or just not for beginners
self.prog_win.show()
self.myLongTask.taskFinished.connect(self.onStart)
self.output_settings()
def onStart(self):
self.progressBar.setRange(0,0)
self.myLongTask.start()
def output_convert(self):
#very long process to convert a txt file to excel
#My Thread
class TaskThread(QtCore.QThread):
taskFinished = QtCore.pyqtSignal()
def run(self):
time.sleep(3)
self.taskFinished.emit()
def run():
app = QtGui.QApplication(sys.argv)
GUI = Window()
app.exec_()
run()
All of the examples and posts here have been very helpful on understanding progress bar implementation but with all the example having specific answers for a specific problem I can't understand the implementation of progress bar in a standard pyqt app. could you guys at least point me in the right direction? Would be appreciated.
This is a very basic progress bar that only uses what is needed at the bare minimum.
It would be wise to read this whole example to the end.
import sys
import time
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar, QPushButton)
TIME_LIMIT = 100
class Actions(QDialog):
"""
Simple dialog that consists of a Progress Bar and a Button.
Clicking on the button results in the start of a timer and
updates the progress bar.
"""
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('Progress Bar')
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 25)
self.progress.setMaximum(100)
self.button = QPushButton('Start', self)
self.button.move(0, 30)
self.show()
self.button.clicked.connect(self.onButtonClick)
def onButtonClick(self):
count = 0
while count < TIME_LIMIT:
count += 1
time.sleep(1)
self.progress.setValue(count)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
sys.exit(app.exec_())
The progress bar is first imported like so from PyQt5.QtWidgets import QProgressBar
Then it is initialized like any other widget in QtWidgets
The line self.progress.setGeometry(0, 0, 300, 25) method defines the x,y positions on the dialog and width and height of the progress bar.
We then move the button using .move() by 30px downwards so that there will be a gap of 5px between the two widgets.
Here self.progress.setValue(count) is used to update the progress. Setting a maximum value using .setMaximum() will also automatically calculated the values for you. For example, if the maximum value is set as 50 then since TIME_LIMIT is 100 it will hop from 0 to 2 to 4 percent instead of 0 to 1 to 2 every second. You can also set a minimum value using .setMinimum() forcing the progress bar to start from a given value.
Executing this program will produce a GUI similar to this.
As you can see, the GUI will most definitely freeze and be unresponsive until the counter meets the TIME_LIMIT condition. This is because time.sleep causes the OS to believe that program has become stuck in an infinite loop.
QThread
So how do we overcome this issue ? We can use the threading class that PyQt5 provides.
import sys
import time
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar, QPushButton)
TIME_LIMIT = 100
class External(QThread):
"""
Runs a counter thread.
"""
countChanged = pyqtSignal(int)
def run(self):
count = 0
while count < TIME_LIMIT:
count +=1
time.sleep(1)
self.countChanged.emit(count)
class Actions(QDialog):
"""
Simple dialog that consists of a Progress Bar and a Button.
Clicking on the button results in the start of a timer and
updates the progress bar.
"""
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('Progress Bar')
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 25)
self.progress.setMaximum(100)
self.button = QPushButton('Start', self)
self.button.move(0, 30)
self.show()
self.button.clicked.connect(self.onButtonClick)
def onButtonClick(self):
self.calc = External()
self.calc.countChanged.connect(self.onCountChanged)
self.calc.start()
def onCountChanged(self, value):
self.progress.setValue(value)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
sys.exit(app.exec_())
Let's break down these modifications.
from PyQt5.QtCore import QThread, pyqtSignal
This line imports Qthread which is a PyQt5 implementation to divide and run some parts(eg: functions, classes) of a program in the background(also know as multi-threading). These parts are also called threads. All PyQt5 programs by default have a main thread and the others(worker threads) are used to offload extra time consuming and process intensive tasks into the background while still keeping the main program functioning.
The second import pyqtSignal is used to send data(signals) between worker and main threads. In this instance we will be using it to tell the main thread to update the progress bar.
Now we have moved the while loop for the counter into a separate class called External.
class External(QThread):
"""
Runs a counter thread.
"""
countChanged = pyqtSignal(int)
def run(self):
count = 0
while count < TIME_LIMIT:
count +=1
time.sleep(1)
self.countChanged.emit(count)
By sub-classing QThread we are essentially converting External into a class that can be run in a separate thread. Threads can also be started or stopped at any time adding to it's benefits.
Here countChanged is the current progress and pyqtSignal(int) tells the worker thread that signal being sent is of type int. While, self.countChanged.emit(count) simply sends the signal to any connections in the main thread(normally it can used to communicate with other worker threads as well).
def onButtonClick(self):
self.calc = External()
self.calc.countChanged.connect(self.onCountChanged)
self.calc.start()
def onCountChanged(self, value):
self.progress.setValue(value)
When the button is clicked the self.onButtonClick will run and also start the thread. The thread is started with .start(). It should also be noted that we connected the signal self.calc.countChanged we created earlier to the method used to update the progress bar value. Every time External::run::count is updated the int value is also sent to onCountChanged.
This is how the GUI could look after making these changes.
It should also feel much more responsive and will not freeze.
The answer to my own question. It is not that hard if you can understand the concept of threading and passing variables through classes. My first mistake was really the lack of knowledge about Worker threads, second is I thought that once you declare the Thread it means that you just have to call it so that it would run the function inside the main class so I was searching on how you would implement that and all I thought was wrong.
Solution
All the hard/Long processes SHOULD be in the subclassed QThread under def run and should be called in your class Window(QtGui.QMainWindow): or main loop and this is what my code look like now
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(750, 450, 400, 200)
self.setFixedSize(self.size())
btn1 = QtGui.QPushButton("Convert", self)
btn1.move(210,171)
btn1.clicked.connect(self.progbar)
def progbar (self):
self.prog_win = QDialog()
self.prog_win.resize(400, 100)
self.prog_win.setFixedSize(self.prog_win.size())
self.prog_win.setWindowTitle("Processing request")
self.lbl = QLabel(self.prog_win)
self.lbl.setText("Please Wait. . .")
self.lbl.move(15,18)
self.progressBar = QtGui.QProgressBar(self.prog_win)
self.progressBar.resize(410, 25)
self.progressBar.move(15, 40)
self.progressBar.setRange(0,1)
self.myLongTask = TaskThread(var = DataYouWantToPass) #initializing and passing data to QThread
self.prog_win.show()
self.onStart() #Start your very very long computation/process
self.myLongTask.taskFinished.connect(self.onFinished) #this won't be read until QThread send a signal i think
def onStart(self):
self.progressBar.setRange(0,0)
self.myLongTask.start()
#added this function to close the progress bar
def onFinished(self):
self.progressBar.setRange(0,1)
self.prog_win.close()
#My Thread
class TaskThread(QtCore.QThread):
taskFinished = QtCore.pyqtSignal()
#I also added this so that I can pass data between classes
def __init__(self, var, parent=None):
QThread.__init__(self, parent)
self.var = var
def run(self):
#very long process to convert a txt file to excel
def run():
app = QtGui.QApplication(sys.argv)
GUI = Window()
app.exec_()
run()
If something in this answer is wrong then please correct me as it would be a great help to understand it more or maybe some dos and don't
Is it possible to get Python to launch a PyQt5 GUI application using the main thread of execution, and then leave the thread open to do other things?
Here is the simplest example I can think of:
from PyQt5.QtWidgets import *
def infinite_useless_loop():
while True:
pass
app = QApplication([])
doodad = QMainWindow()
doodad.show()
infinite_useless_loop()
If you run this code, it freezes because the 'mainloop' is not activated as it normally would be if I didn't make a call to the useless infinite loop, and instead put a call to app.exec_(). The call to infinite_useless_loop is meant to substitute the main thread being used to do other useful work WHILE the GUI is running correctly.
To do this correctly, I'd have to imagine one would make a separate thread and use that to run the GUI, while launching that thread with the main thread, and just continuing on from there; able to communicate if necessary in the future.
EDIT
I wrote some code that gets the main event loop working in a seperate thread, but I am still not able to fully interact with the GUI object that is created in another thread. This code effectively solves half of my problem:
from PyQt5.QtWidgets import *
from threading import Thread
import sys
def makeGUI():
app = QApplication([])
doodad = QMainWindow()
doodad.show()
sys.exit(app.exec_())
def infinite_useless_loop():
while True:
pass
if __name__ == '__main__':
thread = Thread(target=makeGUI)
thread.start()
infinite_useless_loop()
This code spawns the blank window, able to be clicked on and resized correctly (because the event loop is working), but is on its own. Do I have to revert to signals and slots to communicate with the object, or can I somehow get the GUI object back into the main thread and use it's methods from there?
EDIT 2
I do not wish to do any actual GUI manipulation outside of the GUI's thread, just call methods on the GUI object that I HAVE MADE. The entire GUI structure would be made by the GUI object.
EDIT 3
This new code now makes it able to be communicated with, but only with raw bits of information, and not by one thread accessing another thread's GUI object's methods. This is close, but its still not what I want. Calling the GUI object's methods would have to be done by passing the raw information through the queue, and then being processed at the other side. Then an exec() function would have to be called. That is not acceptable. Here is the code:
from PyQt5.QtWidgets import *
import multiprocessing
import threading
import sys
import time
import queue
class Application(QMainWindow):
def __init__(self, comms):
super(Application, self).__init__()
self.comms = comms
self.fetchingThread = threading.Thread(target=self.fetchingLoop)
self.labelOne = QLabel(self)
self.layout = QVBoxLayout()
self.layout.addWidget(self.labelOne)
self.setLayout(self.layout)
self.fetchingThread.start()
def fetchingLoop(self):
while True:
try:
obj = self.comms.get()
self.processItem(obj)
except queue.Empty:
pass
def processItem(self, item):
self.labelOne.setText(str(item))
print(self.labelOne.text())
class Launcher(multiprocessing.Process):
def __init__(self, Application):
super(Launcher, self).__init__()
self.comms = multiprocessing.Queue()
self.Application = Application
def get_comms(self):
return self.comms
def run(self):
app = QApplication([])
window = self.Application(self.comms)
window.show()
sys.exit(app.exec_())
def no_thread():
app = QApplication([])
window = Application(False)
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
thread = Launcher(Application)
comms = thread.get_comms()
thread.start()
c = 0
while True:
c += 1
time.sleep(1)
comms.put(c)
I have a problem i can't quite figure out for some time. I have a main window application and a QDialog that should pop out after clicking one of the buttons, but the show() method on QDialog seems to be waiting for the funcion connected to the "clicked()" signal to end!
I want the dialog to show right after calling the QDialog.show() method, not after all the other code instructions in that function...
Of course in my code I am going to replace the sleep(5) part with much more complicated code, but this pictures the problem and the code I put there is irrelevant to the issue, i think (database connections and updates)
being more specific:
# -*- coding: utf-8 -*-
import sys
import PyQt4
from PyQt4 import QtCore, QtGui
from twython import Twython, TwythonError
from project import Ui_MainWindow
from time import sleep
import psycopg2, globalvals, updater
import updating, noconnection
class UpWindow(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint)
self.ui = updating.Ui_updating()
self.ui.setupUi(self)
class NoConnection(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint)
self.ui = noconnection.Ui_noConnection()
self.ui.setupUi(self)
QtCore.QObject.connect(self.ui.noConnectionClose, QtCore.SIGNAL("clicked()"), self.close)
class MyCounter(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.noConn = NoConnection(self)
self.upWin = UpWindow(self)
QtCore.QObject.connect(self.ui.refreshButton,QtCore.SIGNAL("clicked()"), self.refresh)
QtCore.QObject.connect(self.ui.manageButton,QtCore.SIGNAL("clicked()"), self.manage)
def refresh(self):
self.upWin.show()
self.upWin.show
self.upWin.setVisible(True)
self.setEnabled(False)
self.upWin.setEnabled(True)
#Thats the issue - the sleep instruction is being held
#BEFORE the showing of upWin QDialog
sleep(5)
def manage(self):
print 'ok'
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = MyCounter()
myapp.upcontent()
myapp.show()
sys.exit(app.exec_())
Think of the any Qt program as a cooperative-multitasking system. Graphics and events in general are handled by the main loop. You don't want to stay long in any slot, because the library won't process signals (say button clicks, repaints, but also other stuff) in the mean time.
If you want to do some heavy processing, or anything that needs to wait for resources while the rest of the program is chugging along, use a QThread.
Another option is to force the event processing with qApp.processEvents() (you can find qApp in QtGui), just before your sleep(5) (or whatever code you're going to put in place of it).
Edit: Now, keep in mind that forcing the event processing will just show the QDialog you're trying to popup. You can't do anything with it (remember, no event processing) without calling again qApp.processEvents() or returning from the slot.
If MyCounter represents a widget that does a long computation and updates a dialog during that time, then sleep(5) is not representative of it, because during those 5 seconds the GUI can't handle events. For a "long running" function you would move the blocking part to a QThread and either poll the thread or connect to a signal it emits as it progresses, either way you would not hold up the GUI event loop during that time (for example, the polling, which takes very little time, would occur in an idle callback). The simplest way to create your test would be to use a timed callback into your MyCounter:
def refresh(self):
... show stuff, then:
self.timer = QTimer()
self.timer.timeout.connect(self.updateDialog)
timer.start(100) # 10 times per sec
def updateDialog(self):
#get thread status
if self.thread.status != self.oldStatus:
self.upWin.updateStatus( self.thread.status )
I'm using pyside but (I think) is a generic Qt question.
I know that QThread implementation calls ._exec() method so we should have an event loop on a started QThread. This way we can use QTimer on that thread (I've done this and it works perfectly). My problem is when QWaitCondition is also used, I'd like to have a "consumer" thread with a infinite loop waiting to be notify (from producers) on the QWaitCondition. The problem I have is that with this design I cannot use QTimer inside the consumer Thread.
This is a snippet of the scenario I'm trying to explain:
from PySide import QtGui
from PySide import QtCore
import sys
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.button = QtGui.QPushButton(self)
self.button.setText("Periodical")
self.button.clicked.connect(self.periodical_call)
self.thread = QtCore.QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.loop)
self.thread.start()
def closeEvent(self, x):
self.worker.stop()
self.thread.quit()
self.thread.wait()
def periodical_call(self):
self.worker.do_stuff("main window") # this works
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.do_stuff) # this also works
self.timer.start(2000)
def do_stuff(self):
self.worker.do_stuff("timer main window")
class Worker(QtCore.QObject):
def do_stuff_timer(self):
do_stuff("timer worker")
def do_stuff(self, origin):
self.origin = origin
self.wait.wakeOne()
def stop(self):
self._exit = True
self.wait.wakeAll()
def loop(self):
self.wait = QtCore.QWaitCondition()
self.mutex = QtCore.QMutex()
self._exit = False
while not self._exit:
self.wait.wait(self.mutex)
print "loop from %s" % (self.origin,)
self.timer = QtCore.QTimer()
self.timer.setSingleShot(True)
self.timer.timeout.connect(self.do_stuff_timer)
self.timer.start(1000) # <---- this doesn't work
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
frame = MainWindow()
frame.show()
sys.exit(app.exec_())
Once you click the button we obtain an output like this:
loop from main window
loop from timer main window
loop from timer main window
loop from timer main window
...
This means that the QTimer created inside loop() method is never executed by the event loop.
If I change the design from QWaitCondition to Signals (which is better design imho) the QTimer works, but I'd like to know why they are not working when QWaitCondition is used.
To still process events in a long running task (aka a continuous loop) you need to call QCoreApplication::processEvents().
This will essentially go through all of the queued up slots for your thread.
Calling this function is also necessary for signals (if they are a QueuedConnection signal/slot connection) to make it out of the current thread and into another one.
For PySides, you will need to call PySide.QtCore.QCoreApplication.processEvents()
Your method loop completely occupies thread.
It doesn't return control to event loop. Timer sends its events to event loop which doesn't gain control.
IMO your wile loop is faulty.
One way to fix it is add QApplication.processEvents() in loop (bad approach).
I think you want something else, here are my corrections:
def loop(self):
self.timer = QtCore.QTimer()
self.timer.setSingleShot(False)
self.timer.timeout.connect(self.do_stuff_timer)
self.timer.start(1000)
def stop(self):
self.timer.stop()
this will invoke do_stuff_timer every second until you will call stop.