I’ve got some issues with threading in my program. I have a main program and also a GUI-Class, created with PyQt. I’ve created a small example of my problems.
When I start the python script and press start, the progress bar is working fine, but I get the following warnings:
QBackingStore::endPaint() called with active painter on backingstore paint device
QObject::setParent: Cannot set parent, new parent is in a different thread
These alarms occur due to different threads. But to be honest, I have no idea how to handle the objects, like this progress bar of the gui-class.
I’ve tried many ways but haven’t found a good solution for this.
By pressing start, it starts, by pressing stop the progress bar stops. But here is the next issue, mostly my kernel dies when I press stop. I guess it’s connected to the same problem with the threading?!
The reason for the threading is so that the script can still be interacted with while it is running.
Attached are my two python files: my program and also the gui.
MAINPROGRAM:
#MAINPROGRAM
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication
import sys
import GUI
import threading
import time
class exampleprogram(QtWidgets.QMainWindow, GUI.Ui_MainWindow):
def __init__(self, parent=None):
super(exampleprogram, self).__init__(parent)
self.setupUi(self)
self.pushButton_Start.clicked.connect(self.start)
self.pushButton_Stop.clicked.connect(self.stop)
self.running = False
def start(self):
if(self.running == False):
print("Start")
self.thread = threading.Thread(target=self.run, args=())
self.thread.start()
def stop(self):
print("Stop")
self.running = False
def run(self):
self.running = True
x = 0
thread = threading.currentThread()
while getattr(thread, "do_run", True):
self.thread.do_run = self.running
if(x == 100):
thread.do_run = False
self.progressBar.setValue(x)
time.sleep(0.1)
x = x+1
self.stop()
def main():
app = QApplication(sys.argv)
form = exampleprogram()
form.show()
app.exec_()
if __name__ == '__main__':
main()
GUI:
#GUI
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(573, 92)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.layoutWidget = QtWidgets.QWidget(self.centralwidget)
self.layoutWidget.setGeometry(QtCore.QRect(20, 20, 195, 30))
self.layoutWidget.setObjectName("layoutWidget")
self.formLayout_3 = QtWidgets.QFormLayout(self.layoutWidget)
self.formLayout_3.setContentsMargins(0, 0, 0, 0)
self.formLayout_3.setObjectName("formLayout_3")
self.pushButton_Start = QtWidgets.QPushButton(self.layoutWidget)
self.pushButton_Start.setObjectName("pushButton_Start")
self.formLayout_3.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.pushButton_Start)
self.pushButton_Stop = QtWidgets.QPushButton(self.layoutWidget)
self.pushButton_Stop.setObjectName("pushButton_Stop")
self.formLayout_3.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.pushButton_Stop)
self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
self.progressBar.setGeometry(QtCore.QRect(230, 20, 311, 23))
self.progressBar.setProperty("value", 0)
self.progressBar.setObjectName("progressBar")
MainWindow.setCentralWidget(self.centralwidget)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Test Gui"))
self.pushButton_Start.setText(_translate("MainWindow", "Start"))
self.pushButton_Stop.setText(_translate("MainWindow", "Stop"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
Does anyone have an idea, how to fix it?
EDIT: After stopping the run-function it should be possible to run it again by pressing the start button. Therefore it doesn't matter if it was stopped by the counter x or by pressing the stop button. It's just important not too start multiple times the function.
Many thanks in advance!
Andrew
Whilst it's possible to use Python threads with PyQt, it's generally better to use QThread with a separate worker object, since it more easily allows thread-safe communication between the worker thread and main thread via signals and slots. One of the main problems with your example is precisely that you are attempting to perform GUI-related operations outside of the main thread, which is not supported by Qt (hence the warning messages).
If you want to start, stop and pause the worker from the GUI, it will require some careful handling to ensure the worker and thread shut down cleanly if the user tries to close the window while they are still running. In the example below, I have used a simple abort mechanism in the closeEvent; if you want more sophisticated handling, you could, for example, ignore() the close-event while the worker is still running. Pressing Start will start/contine the worker, and pressing Stop will pause it. Once the worker has finished, pressing Start will completely re-start it (i.e. it will go back to zero):
class Worker(QtCore.QObject):
progress = QtCore.pyqtSignal(int)
finished = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
self._paused = True
self._count = -1
def start(self):
self._paused = False
if self._count < 0:
print('Start')
self._run()
else:
print('Continue')
def stop(self, *, abort=False):
self._paused = True
if abort:
print('Abort')
self._count = -1
else:
print('Pause')
def _run(self):
self._count = 0
self._paused = False
while 0 <= self._count <= 100:
if not self._paused:
QtCore.QThread.msleep(100)
self._count += 1
self.progress.emit(self._count)
self.stop(abort=True)
self.finished.emit()
print('Finished')
class exampleprogram(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(exampleprogram, self).__init__(parent)
self.setupUi(self)
self.thread = QtCore.QThread(self)
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.start)
self.worker.finished.connect(self.thread.quit)
self.worker.progress.connect(self.progressBar.setValue)
self.pushButton_Start.clicked.connect(self.start)
self.pushButton_Stop.clicked.connect(self.stop)
def start(self):
if self.thread.isRunning():
self.worker.start()
else:
self.thread.start()
def stop(self):
if self.thread.isRunning():
self.worker.stop()
def closeEvent(self, event):
self.worker.stop(abort=True)
self.thread.quit()
self.thread.wait()
Related
I have spent a few days trying to get a separate thread to read the incoming serial data as if I loop through a function in the GUI class with a timer the GUI gets unresponsive as it tries to receive the data. I have managed to print the incoming serial on the console even though it has been done using a while loop, which I do not like too much, and I plan to replace it with a timer.
Now I have troubles passing the variable decoded_serial from the WorkerThread class to the displaySerial function in the main GUI class.
This is the code I have been working on:
from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.QtCore import QThread, QTimer
import serial
class WorkerThread(QThread):
def run(self):
ser= serial.Serial(port='COM5', baudrate=9600)
while (True):
if (ser.inWaiting() > 0):
data_string = ser.read(ser.inWaiting()).decode('utf-8')
decoded_serial = (data_string)
print(decoded_serial)
class Ui_MainWindow(object):
def workerSignal(self):
self.worker.start()
print('Worker started')
def setupUi(self, MainWindow):
self.worker = WorkerThread()
MainWindow.setObjectName('MainWindow')
MainWindow.resize(250, 300)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName('centralwidget')
self.groupBox = QtWidgets.QGroupBox(self.centralwidget)
self.groupBox.setGeometry(QtCore.QRect(30, 100, 201, 161))
self.groupBox.setObjectName('groupBox')
self.LineEdit = QtWidgets.QLineEdit(self.groupBox)
self.LineEdit.setGeometry(QtCore.QRect(10, 50, 181, 20))
self.LineEdit.setObjectName('LineEdit')
self.Label = QtWidgets.QLabel(self.groupBox)
self.Label.setGeometry(QtCore.QRect(10, 30, 181, 10))
self.Label.setObjectName('Label')
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate('MainWindow', 'TestApp'))
self.groupBox.setTitle(_translate('MainWindow', 'Serial'))
self.Label.setText(_translate('MainWindow', 'Serial'))
def displaySerial(self):
self.LineEdit.setText(decoded_serial)
if __name__ == '__main__':
import sys
def eventTimer():
ui.workerSignal()
def serialLoop():
ui.displaySerial()
app = QtWidgets.QApplication(sys.argv)
QtCore.QTimer.singleShot(0, eventTimer)
serial_timer = QtCore.QTimer()
serial_timer.timeout.connect(serialLoop)
serial_timer.start(1000)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec())
To try to pass the value to the main GUI class I have tried something like:
class WorkerThread(QThread):
def run(self):
WorkerThread.data = decoded_serial
class Ui_MainWindow(object, WorkerThread):
print(WorkerThread.data)
Although I do not seem to be doing it the right way, and it returns some errors.
What would be the best approach in order to send the retrieved serial string to the line edit field?
I have had a look at multiple solutions presented in the past, but I do not seem to understand how to implement those in my code.
Any guidance is highly appreciated!
I have a GUI made in Designer (pyqt5). A function in my main class needs to work on a separate thread. I also catch the stdout on a QtextEdit LIVE during operations. Everything so far works.
Right now I'm trying to implement a ProgressBar onto my main GUI form. The bar needs to show live progression just like it does on the textEdit.
The example code below works on Linux without any warnings. But on Windows I get the error:
QObject::setParent: Cannot set parent, new parent is in a different thread
I know that this is due to me having a ui element modification within my threaded function. I did my research but all the answers point to using QThreads (just when I started to understand basic threading!). I would prefer a way to update my GUI without having to change the current threading system below.
Here is the example code:
import sys
import threading
import time
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QTextCursor
from ui_form import Ui_Form
class EmittingStream(QObject):
textWritten = pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
class Form(QMainWindow):
finished = pyqtSignal()
def __init__(self, parent=None):
super(Form, self).__init__(parent)
# Install the custom output stream
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)
self.ui = Ui_Form()
self.ui.setupUi(self)
self.ui.pushButton_run.clicked.connect(self.start_task)
self.finished.connect(self.end_task)
def start_task(self):
self.thread = threading.Thread(target=self.run_test)
self.thread.start()
self.ui.pushButton_run.setEnabled(False)
def end_task(self):
self.ui.pushButton_run.setEnabled(True)
def __del__(self):
# Restore sys.stdout
sys.stdout = sys.__stdout__
def normalOutputWritten(self, text):
"""Append text to the QTextEdit."""
cursor = self.ui.textEdit.textCursor()
cursor.movePosition(QTextCursor.End)
cursor.insertText(text)
self.ui.textEdit.setTextCursor(cursor)
self.ui.textEdit.ensureCursorVisible()
def run_test(self):
for i in range(100):
per = i + 1
self.ui.progressBar.setValue(per)
print("%%%s" % per)
time.sleep(0.15) # simulating expensive task
print("Task Completed!")
time.sleep(1.5)
self.ui.progressBar.reset()
self.finished.emit()
def main():
app = QApplication(sys.argv)
form = Form()
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
the ui:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'form.ui'
#
# Created: Mon Apr 30 13:43:19 2018
# by: PyQt5 UI code generator 5.2.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(Form)
self.centralwidget.setObjectName("centralwidget")
self.pushButton_run = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_run.setGeometry(QtCore.QRect(40, 20, 311, 191))
self.pushButton_run.setObjectName("pushButton_run")
self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
self.textEdit.setGeometry(QtCore.QRect(40, 230, 721, 241))
self.textEdit.setObjectName("textEdit")
self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
self.progressBar.setGeometry(QtCore.QRect(40, 490, 721, 23))
self.progressBar.setObjectName("progressBar")
Form.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(Form)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 25))
self.menubar.setObjectName("menubar")
Form.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(Form)
self.statusbar.setObjectName("statusbar")
Form.setStatusBar(self.statusbar)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "MainWindow"))
self.pushButton_run.setText(_translate("Form", "RUN"))
Somehow I need to -instantly- inform the gui thread (from my running thread) that the progress bar value is changing (a process that could take up minutes to complete).
Define a custom signal that sends updates to the progress-bar:
class Form(QMainWindow):
finished = pyqtSignal()
updateProgress = pyqtSignal(int)
def __init__(self, parent=None):
super(Form, self).__init__(parent)
...
self.updateProgress.connect(self.ui.progressBar.setValue)
def run_test(self):
for i in range(100):
per = i + 1
self.updateProgress.emit(per)
...
I am trying to run a progress bar on a thread and a function on another thread. The following is my approach and it is working fine, until I add a QMessageBox.
I created two new classes for QThread, one handles the progress bar, another my function. They are being called when the button is pressed using the onButtonClicked function
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QMessageBox, QLineEdit, QProgressBar, QLabel, QFileDialog, QCheckBox, QMenuBar, QStatusBar
import time
TIME_LIMIT = 100
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.msg = QMessageBox()
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(300, 60, 47, 13))
self.label.setObjectName("label")
self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit.setGeometry(QtCore.QRect(270, 100, 113, 20))
self.lineEdit.setObjectName("lineEdit")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(290, 150, 75, 23))
self.pushButton.setObjectName("pushButton")
self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
self.progressBar.setGeometry(QtCore.QRect(280, 210, 118, 23))
self.progressBar.setProperty("value", 24)
self.progressBar.setObjectName("progressBar")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.Actionlistenr()
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label.setText(_translate("MainWindow", "TextLabel"))
self.pushButton.setText(_translate("MainWindow", "PushButton"))
def Actionlistenr(self):
self.pushButton.clicked.connect(self.onButtonClick)
def test(self):
if self.lineEdit.text() == "":
self.msg.setIcon(QMessageBox.Critical)
self.msg.setText("Please select a document first!")
self.msg.setWindowTitle("Error")
return self.msg.exec()
# If this was just a regular print statement,
# then it would work, or any other statement that
# does not involve a QMessageBox
def onButtonClick(self):
self.calc = External()
self.calc.countChanged.connect(self.onCountChanged)
self.calc.start()
self.calc2 = External2(self)
self.calc2.start()
def onCountChanged(self, value):
self.progressBar.setValue(value)
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 External2(QThread, object):
"""
Runs a counter thread.
"""
def __init__(self, outer_instance):
super().__init__()
self.outer_instance = outer_instance
def run(self):
self.outer_instance.test()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
I am getting QObject::setParent: Cannot set parent, new parent is in a different thread Error when doing this, only when I add a QMessageBox in my test function. I am assuming that this is happening because QMessagebox is running on the main thread, not my External2() class, how can I fix this?
The first thing you have to do is verify if the requirements are met (in this case the QLineEdit is not empty) for the heavy duty to start. In these cases I prefer to use the worker thread approach. To launch the heavy task, the method must be invoked asynchronously, for example using QTimer.singleShot(), and to pass the additional arguments use functools.partial(), you must also use #pyqtSlot to be sure that the tasks are executed in the thread Right.
On the other hand you should not modify the class generated by Qt Designer(1) but create another class that inherits from the widget and use the first one to fill it.
from PyQt5 import QtCore, QtGui, QtWidgets
from functools import partial
import time
TIME_LIMIT = 100
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(300, 60, 47, 13))
self.label.setObjectName("label")
self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit.setGeometry(QtCore.QRect(270, 100, 113, 20))
self.lineEdit.setObjectName("lineEdit")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(290, 150, 75, 23))
self.pushButton.setObjectName("pushButton")
self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
self.progressBar.setGeometry(QtCore.QRect(280, 210, 118, 23))
self.progressBar.setProperty("value", 24)
self.progressBar.setObjectName("progressBar")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label.setText(_translate("MainWindow", "TextLabel"))
self.pushButton.setText(_translate("MainWindow", "PushButton"))
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.msg = QtWidgets.QMessageBox()
self.Actionlistenr()
thread = QtCore.QThread(self)
thread.start()
self.m_worker = Worker()
self.m_worker.countChanged.connect(self.progressBar.setValue)
self.m_worker.moveToThread(thread)
def Actionlistenr(self):
self.pushButton.clicked.connect(self.onButtonClick)
def request_information(self):
filename = self.lineEdit.text()
if filename:
wrapper = partial(self.m_worker.task, filename)
QtCore.QTimer.singleShot(0, wrapper)
else:
self.msg.setIcon(QtWidgets.QMessageBox.Critical)
self.msg.setText("Please select a document first!")
self.msg.setWindowTitle("Error")
self.msg.exec_()
#QtCore.pyqtSlot()
def onButtonClick(self):
self.request_information()
class Worker(QtCore.QObject):
countChanged = QtCore.pyqtSignal(int)
#QtCore.pyqtSlot(str)
def task(self, filename):
# execute heavy task here
print("start")
print(filename)
count = 0
while count < TIME_LIMIT:
count += 1
time.sleep(1)
self.countChanged.emit(count)
print("finished")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
(1) http://pyqt.sourceforge.net/Docs/PyQt5/designer.html
Okay this per-sae does a bit more than you asked but it also does a bit less also -- however it does include all the bits and pieces and how they interconnect so you should be able to extrapolate from this and get it to do whatever it is you are wanting to get done. Note your program had a few issues so I could not duplicate it verbatim -- one of those issues pertaining to your specific problem is that you cannot run anything that inherits from QWidgets from within a thread -- I solved this by creating a multi-process to handle the 2nd Window situation. Still with how I have outlined the Threading you can see that you do not need to have that QMessageBox within the Thread but you could cause something from within that Thread to launch the QMessageBox back in the QMainWindow -- as I have pointed out -- if you need the Thread to launch that QMessageBox -- This works although you might need to add a bit to show the functionality going on within the Threads -- I know this because I have already tested that aspect of this.
from sys import exit as sysExit
from time import sleep as tmSleep
from PyQt5.QtCore import Qt, QObject, QThread, QRunnable, pyqtSignal, pyqtSlot
# from PyQt5.QtGui import ??
#Widget Container Objects
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QDockWidget
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QMenuBar, QStatusBar, QLabel
#Widget Action Objects
from PyQt5.QtWidgets import QMessageBox, QFileDialog, QPushButton, QLineEdit
from PyQt5.QtWidgets import QProgressBar, QCheckBox, QAction, QStyleFactory
# Part of Threading
# Note be very careful with Signals/Slots as they are prone to Memory Leaks
class ThreadSignals(QObject):
ObjctSignal = pyqtSignal(object)
IntgrSignal = pyqtSignal(int)
# Part of Threading -- if its a Class that does pretty much the same thing then should only have one
class Processor(QWidget):
def __init__(self, Id):
QWidget.__init__(self)
self.ThreadActive = True
self.RunProcess = False
self.Id = Id
self.Name = '---- Threaded Process ' + str(self.Id)
self.Msg = self.Name
def Connect(self, sigHandle, sigFlag):
self.QueData = queQue()
cnt = 0
self.Flag = sigFlag
sigHandle.emit(self)
tmSleep(0.005) # 5 Milliseconds
# This simulates a continuously running process
# The waits are necessary to allow the OS to do stuff because
# python IS NOT multiprocessing due to the GIL -- look it up
self.lstData = []
while self.ThreadActive:
while self.RunProcess:
cnt += 1
if cnt % 10 == 0:
self.lstData.append(cnt)
if cnt % 100 == 0:
self.Msg = self.Name + ' : Loop ' + str(cnt)
self.QueData.put(self.Msg)
self.QueData.put(self.lstData.copy())
sigFlag.emit(cnt)
self.lstData = []
tmSleep(0.005) # 5 Milliseconds
tmSleep(0.005) # 5 Milliseconds
def GetData(self):
RetData = []
if not self.QueData.empty():
RetData = list(self.QueData.get())
return RetData
def StartProcess(self):
self.RunProcess = True
self.Msg = self.Name + ' Started'
self.Flag.emit(-1)
def StopProcess(self):
self.RunProcess = False
self.Msg = self.Name + ' Stopped'
self.Flag.emit(-1)
def DisConnect(self):
self.RunProcess = False
self.ThreadActive = False
self.Msg = self.Name + ' Disconnected'
# Part of Threading -- if its a Class that does pretty much the same thing then should only have one
class WorkerProcess(QRunnable):
def __init__(self, StartrFunc, Id):
super(WorkerProcess, self).__init__()
self.StartrFunc = StartrFunc
# def StarterFunc(self):
# self.ProcessObject = Processor(#)
# self.ProcessObject.Connect(sigHandle, sigFlag)
self.setAutoDelete(False)
self.Id = Id
self.name = '----- WorkerProcess ' + str(Id)
# Create Signal (aka Sender) Here
self.signals = ThreadSignals()
self.sigHndl = self.signals.ObjctSignal
self.sigFlag = self.signals.IntgrSignal
#pyqtSlot()
def run(self):
print('Inside ',self.name)
self.StartrFunc(self.sigHndl, self.sigFlag)
# def StarterFunc(self):
# self.ProcessObject = Processor(#)
# self.ProcessObject.Connect(sigHandle, sigFlag)
print('******************************')
print('--- Process Completed')
# Note while this process has completed this thread is still active
def DisConnect(self):
# This disconnects all of its Signals
self.signals.disconnect()
# This is your Menu and Tool Bar class it does not handle the Tool Bar
# at this time but it could be expanded to do so fairly easily just
# keep in mind everything on a Tool Bar comes from the Menu Bar
class MenuToolBar(QDockWidget):
def __init__(self, parent):
QDockWidget.__init__(self)
self.Parent = parent
self.MainMenu = parent.menuBar()
# This is used to have a handle to the Menu Items
# should you implement a Tool Bar
self.MenuActRef = {'HelloAct':0,
'ResetAct':0}
# ******* Create the World Menu *******
self.WorldMenu = self.MainMenu.addMenu('World')
# ******* Create World Menu Items *******
self.HelloAct = QAction('&Hello', self)
# In case you have or want to include an Icon
# self.HelloAct = QAction(QIcon('Images/hello.ico'), '&Hello', self)
self.HelloAct.setShortcut("Ctrl+H")
self.HelloAct.setStatusTip('Say Hello to the World')
self.HelloAct.triggered.connect(self.SayHello)
self.MenuActRef['HelloAct'] = self.HelloAct
self.ResetAct = QAction('&Reset', self)
# self.ResetAct = QAction(QIcon('Images/reset.ico'), '&Hello', self)
self.ResetAct.setShortcut("Ctrl+H")
self.ResetAct.setStatusTip('Reset the Dialog')
self.ResetAct.triggered.connect(self.ResetWorld)
self.MenuActRef['ResetAct'] = self.ResetAct
# ******* Setup the World Menu *******
self.WorldMenu.addAction(self.HelloAct)
self.WorldMenu.addSeparator()
self.WorldMenu.addAction(self.ResetAct)
self.InitToolBar()
def InitToolBar(self):
# If you create a Tool Bar initialize it here
pass
# These are the Menu/Tool Bar Actions
def SayHello(self):
self.Parent.MenuSubmit()
def ResetWorld(self):
self.Parent.MenuReset()
# Its easiest and cleaner if you Class the Center Pane
# of your MainWindow object
class CenterPanel(QWidget):
def __init__(self, parent):
QWidget.__init__(self)
self.Parent = parent
self.Started = False
#-----
self.lblTextBox = QLabel()
self.lblTextBox.setText('Text Box Label')
#-----
self.lneTextBox = QLineEdit()
#-----
self.btnPush = QPushButton()
self.btnPush.setText('Start')
self.btnPush.clicked.connect(self.Starter)
#-----
self.btnTest = QPushButton()
self.btnTest.setText('Test')
self.btnTest.clicked.connect(self.TestIt)
#-----
HBox = QHBoxLayout()
HBox.addWidget(self.btnPush)
HBox.addWidget(self.btnTest)
HBox.addStretch(1)
#-----
self.pbrThusFar = QProgressBar()
self.pbrThusFar.setProperty('value', 24)
#-----
VBox = QVBoxLayout()
VBox.addWidget(self.lblTextBox)
VBox.addWidget(self.lneTextBox)
VBox.addWidget(QLabel(' ')) # just a spacer
VBox.addLayout(HBox)
VBox.addWidget(QLabel(' ')) # just a spacer
VBox.addWidget(self.pbrThusFar)
VBox.addStretch(1)
#-----
self.setLayout(VBox)
def Starter(self):
if self.Started:
self.btnPush.setText('Start')
self.Started = False
self.Parent.OnStart()
else:
self.btnPush.setText('Reset')
self.Started = True
self.pbrThusFar.setProperty('value', 24)
self.Parent.OnReset()
def TestIt(self):
# Note this cannot be handled within a Thread but a Thread can be
# designed to make a call back to the MainWindow to do so. This
# can be managed by having the MainWindow pass a handle to itself
# to the Thread in question or by using a Signal/Slot call from
# within the Thread back to the MainWindow either works
Continue = True
if self.lneTextBox.text() == '':
DocMsg = QMessageBox()
DocMsg.setIcon(QMessageBox.Critical)
DocMsg.setWindowTitle("Error")
DocMsg.setText("There is no Document. Do you want to Quit?")
DocMsg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
DocMsg.setDefaultButton(QMessageBox.No)
DocMsg.setWindowFlags(Qt.WindowStaysOnTopHint)
MsgReply = DocMsg.exec_()
if MsgReply == QMessageBox.Yes:
sysExit()
def HandleSubmit(self):
self.lneTextBox.setText('Center Panel Menu Submit')
# This is sort of your Main Handler for your interactive stuff as such
# it is best to restrict it to doing just that and let other classes
# handle the other stuff -- it also helps maintain overall perspective
# of what each piece is designed for
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle('Main Window')
# Sometimes its best to place the window where you want but just setting its size works too
# Still I do this in two lines to make it clear what each position is
WinLeft = 150; WinTop = 150; WinWidth = 400; WinHight = 200
# self.setGeometry(WinLeft, WinTop, WinWidth, WinHight)
self.resize(WinWidth, WinHight)
self.CenterPane = CenterPanel(self)
self.setCentralWidget(self.CenterPane)
# The Menu and Tool Bar for your MainWindow should be classed as well
self.MenuBar = MenuToolBar(self)
self.SetStatusBar(self)
# Not exactly sure what all this does yet but it does remove
# oddities from the window so I always include it - for now
self.setStyle(QStyleFactory.create('Cleanlooks'))
# Part of Threading
self.Thread1Connected = False
self.Thread2Connected = False
# Create Handles for the Threads
# I used this methodology as it was best for my program but
# there are other ways to do this it depends on your needs
self.Thread1Hndl = QObject()
self.Thread2Hndl = QObject()
# This is used to start the Thread 1
self.MyThread1 = WorkerProcess(self.Threader1, 1)
# Create Slots (aka Receivers) Here
self.MyThread1.signals.ObjctSignal.connect(self.Thread1_Hndl)
self.MyThread1.signals.IntgrSignal.connect(self.Thread1_Flag)
# This is used to start the Thread 2
self.MyThread2 = WorkerProcess(self.Threader2, 2)
# Create Slots (aka Receivers) Here
self.MyThread2.signals.ObjctSignal.connect(self.Thread2_Hndl)
self.MyThread2.signals.IntgrSignal.connect(self.Thread2_Flag)
def MenuSubmit(self):
self.CenterPane.HandleSubmit()
def MenuReset(self):
self.CenterPane.lineEdit.setText('Main Window Menu Reset')
def SetStatusBar(self, parent):
StatusMsg = ''
parent.StatBar = parent.statusBar()
if len(StatusMsg) < 1:
# This verbiage will disappear when you view menu items
StatusMsg = 'Ready'
parent.StatBar.showMessage(StatusMsg)
def OnStart(self):
if self.Thread1Connected:
self.Thread1Hndl.StartProcess()
if self.Thread2Connected:
self.Thread2Hndl.StartProcess()
def OnReset(self):
pass
# Part of Threading
def Thread1_Hndl(self, sigHandle):
self.Thread1Hndl = sigHandle
print('******************************')
print('--- Thread 1 Handle Sent Back Validation')
print(self.Thread1Hndl.Msg)
self.Thread1Connected = True
def Thread1_Flag(self, sigFlag):
print('******************************')
print('--- Thread 1 Loop Id Sent Back Validation')
print('----- Current Loop : ', sigFlag)
print(self.Thread1Hndl.Msg)
self.DoStuffT1()
if sigFlag > 1000:
self.Thread1Connected = False
self.Thread1Hndl.DisConnect()
print(self.Thread1Hndl.Msg)
def Thread2_Hndl(self, Handle):
self.Thread2Hndl = Handle
print('******************************')
print('--- Thread 2 Handle Sent Back Validation')
print(self.Thread2Hndl.Msg)
self.Thread2Connected = True
def Thread2_Flag(self, sigFlag):
print('******************************')
print('--- Thread 2 Loop Id Sent Back Validation')
print('----- Current Loop : ', sigFlag)
print(self.Thread2Hndl.Msg)
self.DoStuffT2()
if sigFlag > 1000:
self.Thread2Connected = False
self.Thread2Hndl.DisConnect()
print(self.Thread2Hndl.Msg)
def DoStuffT1(self):
# Just a place holder function for demonstration purposes
# Perhaps handle this here for one of the Threads
# self.CenterPane.pbrThusFar.setValue(value)
pass
def DoStuffT2(self):
# Just a place holder function for demonstration purposes
pass
# Part of Threading
# These Functions are being passed into completely Separate Threads
# do not try to print from within as stdout is not available
# Also keep in mind you cannot use anything within a Thread that
# inherits from QWidgets and a few from QGui as well
# Create the entire object within the Thread allowing for complete
# autonomy of its entire functionality from the Main GUI
def Threader1(self, sigHandle, sigFlag):
self.Thrdr1Obj = Processor(1) # Create Threader 1 Object from Class
self.Thrdr1Obj.Connect(sigHandle, sigFlag)
def Threader2(self, sigHandle, sigFlag):
self.Thrdr2Obj = Processor(2) # Create Threader 2 Object from Class
self.Thrdr2Obj.Connect(sigHandle, sigFlag)
if __name__ == "__main__":
# It is best to keep this function to its bare minimum as its
# main purpose is to handle pre-processing stuff
#
# Next you did not appear to be using sys.argv but if you od need
# to use command line arguments I strongly suggest you look into
# argparse its a python library and very helpful for this as such
# also much cleaner than dealing with them via regular means
MainThred = QApplication([])
MainGUI = MainWindow()
MainGUI.show()
sysExit(MainThred.exec_())
Finally if you have any questions on this do ask but I did try to include explanatories within the code. Also I did a bit of extra cross object calling so you could see how it might be done -- this is not a production version but more a proof of concept that demonstrates numerous concepts within it
I wish to open a PySide/PyQt window and automatically start executing a method, which will show a progress in the UI while it is executing.
With the code below, the ui is not shown until the process has completed, thus you cannot see the progress. How can I change the code to see the progress before the process has completed?
from PySide import QtGui
import time
class MyApp(QtGui.QMainWindow)
def __init__(self, parent=None):
super(MyApp, self).__init__(parent)
# Setup
self.centralWidget = QtGui.QWidget(self)
self.setCentralWidget(self.centralWidget)
self.setup_UI()
# Execute process!
self.process()
def setup_UI(self):
''' Attach widgets to window '''
self.mainLayout=QtGui.QVBoxLayout(self.centralWidget)
self.list_widget = QtGui.QListWidget()
self.progress_bar = QtGui.QProgressBar()
self.mainLayout.addWidget(self.list_widget)
self.mainLayout.addWidget(self.progress_bar)
def process(self):
''' Manipulate the ui '''
self.progress_bar.setMaximum(0)
self.progress_bar.setMaximum(10)
for x in range(0, 10):
time.sleep(1)
self.list_widget.addItem('Item ' + str(x))
self.progress_bar.setValue(x)
my_app = MyApp()
my_app.show()
Your main problem that you are blocking qt main thread by calling time.sleep. To solve this issue you have two options. One of them is using threading. Another option is change your code to asynchronous like this:
from PySide import QtGui, QtCore
import time
import sys
class MyApp(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MyApp, self).__init__(parent)
# Setup
self.centralWidget = QtGui.QWidget(self)
self.setCentralWidget(self.centralWidget)
self.setup_UI()
# Execute process!
self.set_process()
self.timer = QtCore.QTimer()
self.i = 0
self.timer.timeout.connect(self.update)
self.timer.start(1000)
def setup_UI(self):
''' Attach widgets to window '''
self.mainLayout=QtGui.QVBoxLayout(self.centralWidget)
self.list_widget = QtGui.QListWidget()
self.progress_bar = QtGui.QProgressBar()
self.mainLayout.addWidget(self.list_widget)
self.mainLayout.addWidget(self.progress_bar)
def set_process(self):
''' Manipulate the ui '''
self.progress_bar.setMaximum(0)
self.progress_bar.setMaximum(10)
def update(self):
if self.i > 9:
self.timer.stop()
self.list_widget.addItem('Item ' + str(self.i))
self.progress_bar.setValue(self.i)
self.i += 1
def main():
app = QtGui.QApplication(sys.argv)
my_win = MyApp()
my_win.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
This example are using Qtimer object for updating progress bar with delay.
The example can be made to work quite easily by starting the processing with a single-shot timer and then calling processEvents within the loop to update the GUI:
# Execute process!
QtCore.QTimer.singleShot(100, self.process)
...
for x in range(0, 10):
time.sleep(1)
...
QtGui.qApp.processEvents(QtCore.QEventLoop.AllEvents, 50)
However, there is no guarantee that this type of approach will work with a more realistic example. You may end up needing to use threads or multiprocessing - it all depends on the specific kind of processing you are going to do.
I'm trying to write a script which will display a busy indication
while performing the task. And when the task is over, the progress bar
will fill to the end showing that 100% task has been completed.
I just want the progress bar to show a task is going on.But when I start
the task, busy indication stops.It seems to me that the indication and
the task can not continue together. Please help me. Here's mycode:
from PyQt4 import QtCore, QtGui
from time import sleep
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(344, 159)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
self.pb = QtGui.QProgressBar(self.centralwidget)
self.pb.setGeometry(QtCore.QRect(20, 20, 301, 31))
self.pb.setProperty("value", 0)
self.pb.setObjectName(_fromUtf8("pb"))
self.btn = QtGui.QPushButton(self.centralwidget)
self.btn.setGeometry(QtCore.QRect(20, 70, 98, 27))
self.btn.setObjectName(_fromUtf8("btn"))
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtGui.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 344, 25))
self.menubar.setObjectName(_fromUtf8("menubar"))
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtGui.QStatusBar(MainWindow)
self.statusbar.setObjectName(_fromUtf8("statusbar"))
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QObject.connect(self.btn, QtCore.SIGNAL(_fromUtf8("clicked()")), self.action)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def action(self):
self.pb.setRange(0, 0)
sleep(3) # Here I want to run a command.For example: os.system('copy something')
self.pb.setRange(0, 100)
self.pb.setValue(100)
QtGui.qApp.processEvents()
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
self.btn.setText(_translate("MainWindow", "Start", None))
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
MainWindow = QtGui.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
First, it's a bad idea to directly edit the code created with QtDesigner. You may have seen the line # WARNING! All changes made in this file will be lost! at the top of the document. For such a simple widget, you're better off with manual coding.
Secondly, take a closer look at what the action slot actually does.
def action(self):
self.pb.setRange(0, 0) # Un
sleep(3) # <-- Your slot blocks HERE
self.pb.setRange(0, 100)
self.pb.setValue(100)
QtGui.qApp.processEvents()
There is no reason for your progressBar to update its value while your slot is blocked in sleep. When action is called, the slot thread sleeps for 3 sec then sets the progress bar to a full 100.
You can't expect the progressBar to magically update itself while your task is running. If you have no idea how long it will take and you can't subdivise it in steps, you should consider using a pulsed ProgressBar instead (see example 1 below). If you can easily get the progression of your task (say copying n files), you should update the value of your progressBar accordingly.
Either way, you should use QThread to get a non-blocking behaviour, and signals to communicate between your thread(s) and your main application.
The main application starts the QThread implementing the long-running task.
The QThread notifies the task progression (if available) or completion to the main application
Example 1 - Pulse ProgressBar:
If minimum and maximum are both set to 0, the progress bar will show a busy indicator instead of a percentage of steps.
class MyCustomWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(MyCustomWidget, self).__init__(parent)
layout = QtGui.QVBoxLayout(self)
# Create a progress bar and a button and add them to the main layout
self.progressBar = QtGui.QProgressBar(self)
self.progressBar.setRange(0,1)
layout.addWidget(self.progressBar)
button = QtGui.QPushButton("Start", self)
layout.addWidget(button)
button.clicked.connect(self.onStart)
self.myLongTask = TaskThread()
self.myLongTask.taskFinished.connect(self.onFinished)
def onStart(self):
self.progressBar.setRange(0,0)
self.myLongTask.start()
def onFinished(self):
# Stop the pulsation
self.progressBar.setRange(0,1)
class TaskThread(QtCore.QThread):
taskFinished = QtCore.pyqtSignal()
def run(self):
time.sleep(3)
self.taskFinished.emit()
Example 2 - Classic ProgressBar:
class MyCustomWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(MyCustomWidget, self).__init__(parent)
layout = QtGui.QVBoxLayout(self)
self.progressBar = QtGui.QProgressBar(self)
self.progressBar.setRange(0,100)
button = QtGui.QPushButton("Start", self)
layout.addWidget(self.progressBar)
layout.addWidget(button)
button.clicked.connect(self.onStart)
self.myLongTask = TaskThread()
self.myLongTask.notifyProgress.connect(self.onProgress)
def onStart(self):
self.myLongTask.start()
def onProgress(self, i):
self.progressBar.setValue(i)
class TaskThread(QtCore.QThread):
notifyProgress = QtCore.pyqtSignal(int)
def run(self):
for i in range(101):
self.notifyProgress.emit(i)
time.sleep(0.1)
A bit late, but I've written detailed documentation on this very issue since a lot of people seem to face this problem.
Introduction to Progress Bars
Finally I got what I wanted, though little bit editing needed . I just added this line
to onFinished():
self.progressBar.setValue(1)
to confirm 100% task completion.
Here's the code:
from PyQt4 import QtCore, QtGui
from time import sleep
import sys, os
class MyCustomWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(MyCustomWidget, self).__init__(parent)
layout = QtGui.QVBoxLayout(self)
# Create a progress bar and a button and add them to the main layout
self.progressBar = QtGui.QProgressBar(self)
self.progressBar.setRange(0,1)
layout.addWidget(self.progressBar)
button = QtGui.QPushButton("Start", self)
layout.addWidget(button)
button.clicked.connect(self.onStart)
self.myLongTask = TaskThread()
self.myLongTask.taskFinished.connect(self.onFinished)
def onStart(self):
self.progressBar.setRange(0,0)
self.myLongTask.start()
def onFinished(self):
# Stop the pulsation
self.progressBar.setRange(0,1)
self.progressBar.setValue(1)
class TaskThread(QtCore.QThread):
taskFinished = QtCore.pyqtSignal()
def run(self):
os.system('sudo apt-get install leafpad')
self.taskFinished.emit()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MyCustomWidget()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
apply this:
progressbar.setMinimum(0)
progressbar.setMaximum(0)
progressbar.setValue(0)
This setting will have a busy appearance, You do not need to add it to any function, it can be in the class constructor if you want