I have loadData() function which parses big XML file and insert data into QTableWidget. Function is executed when application starts = GUI freezes for a moment and I don't want that. This is my WorkerThread class:
class WorkerThread(QThread):
def __init__(self):
QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
QtApp().loadData()
And this is what I have tried:
class QtApp(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = uic.loadUi('gui.ui', self)
self.ui.show()
self.workerThread = WorkerThread()
self.workerThread.start()
It just loads the main window again and again. Eventually application crashes. Exiting workerThread at the end of loadData() does not help.
Here is a possible solution:
class WorkerThread(QThread):
# define a new signal which will be emitted when data loading finishes
load_data_finished = pyqtSignal(list)
def __init__(self):
QThread.__init__(self)
def run(self):
# load data in new thread
self.load_data()
def load_data(self):
data = []
#
# load your data here ...
# example:
data.append(1)
data.append(2)
data.append(3)
#
# when loading data finishes, emit the signal
self.load_data_finished.emit(data)
class QtApp(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
# load UI...
self.show()
self.workerThread = WorkerThread()
# connect the thread signal to a slot defined in this class
self.workerThread.load_data_finished.connect(self.on_load_data_finished)
self.workerThread.start()
def on_load_data_finished(self, data):
print("data loaded: ", data)
app = QApplication(sys.argv)
main_window = QtApp()
app.exec_()
Related
I'm trying to make the loading ui. I want to show the loading ui when the operation starts in the thread, and when the thread operations is completed, I want to hide the loading ui.
class TableThread(QThread):
set_signal = pyqtSignal(int)
stop_signal = pyqtSignal()
def __init__(self, parent):
super().__init__(parent)
def run(self):
for i in range(10):
time.sleep(0.5)
self.set_signal.emit(i)
self.stop_signal.emit()
class Test(QDialog, form_settings_ui):
def __init__(self):
super().__init__()
self.setupUi(self)
self.tableWidget.setRowCount(0)
self.movie = QMovie('loading.gif', QByteArray(), self)
self.movie.setCacheMode(QMovie.CacheAll)
self.label.setMovie(self.movie)
self.pushButton.clicked.connect(self.clicked_push_button)
def set_table_list(self, i):
self.tableWidget.insertRow(i)
self.tableWidget.setItem(i, 0, QTableWidgetItem(str(i)))
self.tableWidget.setItem(i, 1, QTableWidgetItem(str(i)))
self.tableWidget.resizeColumnsToContents()
def clicked_push_button(self):
self.table_thread = TableThread(self)
self.table_thread.set_signal.connect(self.set_table_list)
self.table_thread.stop_signal.connect(self.stop_loading)
self.table_thread.start()
self.movie.start()
def stop_loading(self):
self.movie.stop()
if __name__ == "__main__":
app = QApplication(sys.argv)
perforce_ui_window = Test()
perforce_ui_window.show()
app.exec_()
This is the test code.
I put in the self.movie.stop () code, but it just only stopped moving, it didn't disappear.
I want to use multiple imported function with arguments that takes some while to run. I want a 'working' progress bar that track the processes of that function. I have followed 2 questions already here.
Connect an imported function to Qt5 progress bar without dependencies
Report progress to QProgressBar using variable from an imported module
The difference is that the thread can take any function which can have arguments. The function also not needs to yield the percent to return to the progressbar. The progressbar always start at 0%.
I copied a snippet from first link and modified it for example purpose.
from external_script import long_running_function
class Actions(QDialog):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('Progress Bar')
self.progress = QProgressBar(self)
self.button = QPushButton('Start', self)
self.show()
self.button.clicked.connect(self.onButtonClick)
def onButtonClick(self):
long_running_function(**kwargs) # This can be any function that takes argument/s
self.progress.setValue(value)
Do not get too complicated with the answers as they are limited to a very particular context. In general the logic is to pass a QObject to it that updates the percentage value and then emits a signal with that value. For example a simple solution is to use the threading module:
import sys
import threading
from PyQt5 import QtCore, QtWidgets
class PercentageWorker(QtCore.QObject):
started = QtCore.pyqtSignal()
finished = QtCore.pyqtSignal()
percentageChanged = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super().__init__(parent)
self._percentage = 0
#property
def percentage(self):
return self._percentage
#percentage.setter
def percentage(self, value):
if self._percentage == value:
return
self._percentage = value
self.percentageChanged.emit(self.percentage)
def start(self):
self.started.emit()
def finish(self):
self.finished.emit()
class FakeWorker:
def start(self):
pass
def finish(self):
pass
#property
def percentage(self):
return 0
#percentage.setter
def percentage(self, value):
pass
import time
def long_running_function(foo, baz="1", worker=None):
if worker is None:
worker = FakeWorker()
worker.start()
while worker.percentage < 100:
worker.percentage += 1
print(foo, baz)
time.sleep(1)
worker.finish()
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.progress = QtWidgets.QProgressBar()
self.button = QtWidgets.QPushButton("Start")
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.button)
lay.addWidget(self.progress)
self.button.clicked.connect(self.launch)
def launch(self):
worker = PercentageWorker()
worker.percentageChanged.connect(self.progress.setValue)
threading.Thread(
target=long_running_function,
args=("foo",),
kwargs=dict(baz="baz", worker=worker),
daemon=True,
).start()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
I'm building an interface on top of some analysis code I've written that executes some SQL and processes the query results. There's logging surrounding a number of the events in this analysis code that I would like to expose to the user. Because the analysis code is rather long-running, and because I don't want the UI to block, thus far I've done this through putting the analysis function in to its own thread.
Simplified example of what I have now (complete script):
import sys
import time
import logging
from PySide2 import QtCore, QtWidgets
def long_task():
logging.info('Starting long task')
time.sleep(3) # this would be replaced with a real task
logging.info('Long task complete')
class LogEmitter(QtCore.QObject):
sigLog = QtCore.Signal(str)
class LogHandler(logging.Handler):
def __init__(self):
super().__init__()
self.emitter = LogEmitter()
def emit(self, record):
msg = self.format(record)
self.emitter.sigLog.emit(msg)
class LogDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
log_txt = QtWidgets.QPlainTextEdit(self)
log_txt.setReadOnly(True)
layout = QtWidgets.QHBoxLayout(self)
layout.addWidget(log_txt)
self.setWindowTitle('Event Log')
handler = LogHandler()
handler.emitter.sigLog.connect(log_txt.appendPlainText)
logger = logging.getLogger()
logger.addHandler(handler)
logger.setLevel(logging.INFO)
class Worker(QtCore.QThread):
results = QtCore.Signal(object)
def __init__(self, func, *args, **kwargs):
super().__init__()
self.func = func
self.args = args
self.kwargs = kwargs
def run(self):
results = self.func(*self.args, **self.kwargs)
self.results.emit(results)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
widget = QtWidgets.QWidget()
layout = QtWidgets.QHBoxLayout(widget)
start_btn = QtWidgets.QPushButton('Start')
start_btn.clicked.connect(self.start)
layout.addWidget(start_btn)
self.setCentralWidget(widget)
self.log_dialog = LogDialog()
self.worker = None
def start(self):
if not self.worker:
self.log_dialog.show()
logging.info('Run Starting')
self.worker = Worker(long_task)
self.worker.results.connect(self.handle_result)
self.worker.start()
def handle_result(self, result=None):
logging.info('Result received')
self.worker = None
if __name__ == '__main__':
app = QtWidgets.QApplication()
win = MainWindow()
win.show()
sys.exit(app.exec_())
This works fine, except that I need to be able to allow the user to stop the execution of the analysis code. Everything I've read indicates that there is no way to interrupt threads nicely, so using the multiprocessing library seems to be the way to go (there's no way to re-write the analysis code to allow for periodic polling, since the majority of time is spent just waiting for the queries to return results). It's easy enough to get the same functionality in terms of executing the analysis code in a way that doesn't block the UI by using multiprocessing.Pool and apply_async.
E.g. replacing MainWindow from above with:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
widget = QtWidgets.QWidget()
layout = QtWidgets.QHBoxLayout(widget)
start_btn = QtWidgets.QPushButton('Start')
start_btn.clicked.connect(self.start)
layout.addWidget(start_btn)
self.setCentralWidget(widget)
self.log_dialog = LogDialog()
self.pool = multiprocessing.Pool()
self.running = False
def start(self):
if not self.running:
self.log_dialog.show()
logging.info('Run Starting')
self.pool.apply_async(long_task, callback=self.handle_result)
def handle_result(self, result=None):
logging.info('Result received')
self.running = False
But I can't seem to figure out how I would go about retrieving the logging output from the child process and passing it to the parent to update the log dialog. I've read through just about every SO question on this as well as the cookbook examples of how to handle writing to a single log file from multiple processes, but I can't wrap my head around how to adapt those ideas to what I'm trying to do here.
Edit
So trying to figure out what might be going on for why I'm seeing different behavior than #eyllanesc I added:
logger = logging.getLogger()
print(f'In Func: {logger} at {id(logger)}')
and
logger = logging.getLogger()
print(f'In Main: {logger} at {id(logger)}')
to long_task and Mainwindow.start, respectively. When I run main.py I get:
In Main: <RootLogger root (INFO)> at 2716746681984
In Func: <RootLogger root (WARNING)> at 1918342302352
which seems to be what was described in this SO question
This idea of using a Queue and QueueHandler though as a solution seems similar to #eyllanesc's original solution
The signals do not transmit data between processes, so for this case a Pipe must be used and then emit the signal:
# other imports
import threading
# ...
class LogHandler(logging.Handler):
def __init__(self):
super().__init__()
self.r, self.w = multiprocessing.Pipe()
self.emitter = LogEmitter()
threading.Thread(target=self.listen, daemon=True).start()
def emit(self, record):
msg = self.format(record)
self.w.send(msg)
def listen(self):
while True:
try:
msg = self.r.recv()
self.emitter.sigLog.emit(msg)
except EOFError:
break
# ...
In case anyone wanders in to this down the road, using QueueHandler and QueueListener leads to a solution that works on Windows as well. Borrowed heavily from this answer to a similar question:
import logging
import sys
import time
import multiprocessing
from logging.handlers import QueueHandler, QueueListener
from PySide2 import QtWidgets, QtCore
def long_task():
logging.info('Starting long task')
time.sleep(3) # this would be replaced with a real task
logging.info('Long task complete')
def worker_init(q):
qh = QueueHandler(q)
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.addHandler(qh)
class LogEmitter(QtCore.QObject):
sigLog = QtCore.Signal(str)
class LogHandler(logging.Handler):
def __init__(self):
super().__init__()
self.emitter = LogEmitter()
def emit(self, record):
msg = self.format(record)
self.emitter.sigLog.emit(msg)
class LogDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.log_txt = QtWidgets.QPlainTextEdit(self)
self.log_txt.setReadOnly(True)
layout = QtWidgets.QHBoxLayout(self)
layout.addWidget(self.log_txt)
self.setWindowTitle('Event Log')
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
widget = QtWidgets.QWidget()
layout = QtWidgets.QHBoxLayout(widget)
start_btn = QtWidgets.QPushButton('Start')
start_btn.clicked.connect(self.start)
layout.addWidget(start_btn)
self.setCentralWidget(widget)
self.log_dialog = LogDialog()
self.running = False
# sets up handler that will be used by QueueListener
# which will update the LogDialoag
handler = LogHandler()
handler.emitter.sigLog.connect(self.log_dialog.log_txt.appendPlainText)
self.q = multiprocessing.Queue()
self.ql = QueueListener(self.q, handler)
self.ql.start()
# main process should also log to a QueueHandler
self.main_log = logging.getLogger('main')
self.main_log.propagate = False
self.main_log.setLevel(logging.INFO)
self.main_log.addHandler(QueueHandler(self.q))
self.pool = multiprocessing.Pool(1, worker_init, [self.q])
def start(self):
if not self.running:
self.log_dialog.show()
self.main_log.info('Run Starting')
self.pool.apply_async(long_task, callback=self.handle_result)
def handle_result(self, result=None):
time.sleep(2)
self.main_log.info('Result received')
self.running = False
def closeEvent(self, _):
self.ql.stop()
if __name__ == '__main__':
app = QtWidgets.QApplication()
win = MainWindow()
win.show()
sys.exit(app.exec_())
Attempting to write a class that will show the progress of a threaded process. I need to use this class for all "file load" operations; however I am having trouble making it global.
fileloader.py:
from PyQt5.QtCore import pyqtSlot, pyqtSignal
from PyQt5.QtWidgets import QDialog
from PyQt5.uic import loadUi
class FileLoader(QDialog):
completeSig = pyqtSignal()
def __init__(self, parent=None):
super(FileLoader, self).__init__(parent)
self.filename = ""
self.clientcode = ""
self.thread = ""
loadUi("GlobalUI/fileloader.ui", self)
self.prgLoader.setValue(0)
#pyqtSlot()
def on_btnCancel_clicked(self):
self.close()
def closeEvent(self, e):
self.thread.stop()
def loadData(self):
self.thread.totalSig.connect(self.prgLoader.setMaximum)
self.thread.countSig.connect(self.prgLoader.setValue)
self.thread.finished.connect(self.completed)
self.thread.start()
def completed(self):
self.completeSig.emit()
self.close()
loader.py
from PyQt5.QtCore import pyqtSignal, QThread
from fileloader import FileLoader
class DataLoader(QThread):
totalSig = pyqtSignal(int)
countSig = pyqtSignal(int)
def __init__(self, parent=None):
super(DataLoader, self).__init__(parent)
self.threadactive = True
self.commitsize = 300
self.rowdata = []
def run(self):
print("Code Will Go Here For Loading the File")
def stop(self):
self.threadactive = False
self.wait()
class PatDataLoader():
def load(self, clientcode, filename):
fl = FileLoader()
fl.clientcode = clientcode
fl.filename = filename
fl.thread = DataLoader()
fl.loadData()
I am calling PatDataLoader.load("test","test.txt") from another module. The problem I am running into is the application crashes with QThread: Destroyed while thread is still running as there seems to be a problem with the thread process I am passing to the fileloader. Am I not putting these pieces together properly?
main.py:
from lmdb.patloader import PatDataLoader
class PPSReportsApp(QMainWindow):
def __init__(self, *args):
super(PPSReportsApp, self).__init__(*args)
loadUi("GlobalUI/ppsreportswindow.ui", self)
#self.showMaximized()
#pyqtSlot()
def on_actionTest_triggered(self):
pl = PatDataLoader()
pl.load("TEST","testfile.txt")
In your code pl is a local variable so it will be deleted when it finishes executing on_actionTest_triggered which is an instant possibly generating that problem. On the other hand, no load should be a static method because it does not use the self. self.thread must be None, it is better than ""
How can you prevent pl from being deleted before it is finished processing?
fl is a QDialog so you can use exec_().
fileloader.py
class FileLoader(QDialog):
completeSig = pyqtSignal()
def __init__(self, parent=None):
super(FileLoader, self).__init__(parent)
self.filename = ""
self.clientcode = ""
self.thread = None
loadUi("GlobalUI/fileloader.ui", self)
self.prgLoader.setValue(0)
#pyqtSlot()
def on_btnCancel_clicked(self):
self.close()
def closeEvent(self, e):
if self.thread:
self.thread.stop()
def loadData(self):
if self.thread:
self.thread.totalSig.connect(self.prgLoader.setMaximum)
self.thread.countSig.connect(self.prgLoader.setValue)
self.thread.finished.connect(self.completed)
self.thread.start()
def completed(self):
self.completeSig.emit()
self.close()
loader.py
class DataLoader(QThread):
totalSig = pyqtSignal(int)
countSig = pyqtSignal(int)
def __init__(self, parent=None):
super(DataLoader, self).__init__(parent)
self.threadactive = True
self.commitsize = 300
self.rowdata = []
def run(self):
self.totalSig.emit(1000)
print("Code Will Go Here For Loading the File")
# emulate process
for i in range(1000):
if self.threadactive:
QThread.msleep(10)
self.countSig.emit(i)
def stop(self):
self.threadactive = False
self.quit()
self.wait()
class PatDataLoader():
#staticmethod
def load(clientcode, filename):
fl = FileLoader()
fl.clientcode = clientcode
fl.filename = filename
fl.thread = DataLoader()
fl.loadData()
fl.exec_() # <---
main.py
#pyqtSlot()
def on_actionTest_triggered(self):
PatDataLoader.load("TEST","testfile.txt")
I'm trying to update the text in a Qt GUI object via a QThread in PyQt but I just get the error QPixmap: It is not safe to use pixmaps outside the GUI thread, then it crashes. I would really appreciate any help, thanks.
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, parent = None):
QMainWindow.__init__(self, parent)
self.setupUi(self)
self.output = Output()
def __del__ (self):
self.ui = None
#pyqtSignature("")
def on_goBtn_released(self):
threadnum = 1
#start threads
for x in xrange(threadnum):
thread = TheThread()
thread.start()
class Output(QWidget, Ui_Output):
def __init__(self, parent = None):
QWidget.__init__(self, parent)
self.setupUi(self)
self.ui = Ui_Output
self.show()
def main(self):
self.textBrowser.append("sdgsdgsgsg dsgdsg dsgds gsdf")
class TheThread(QtCore.QThread):
trigger = pyqtSignal()
def __init__(self):
QtCore.QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
self.trigger.connect(Output().main())
self.trigger.emit()
self.trigger.connect(Output().main())
This line is problematic. You are instantiating a class in the thread which looks like a widget. This is wrong. You shouldn't use GUI elements in a different thread. All GUI related code should run in the same thread with the event loop.
The above line is also wrong in terms of design. You emit a custom signal from your thread and this is a good way. But the object to process this signal should be the one that owns/creates the thread, namely your MainWindow
You also don't keep a reference to your thread instance. You create it in a method, but it is local. So it'll be garbage collected, you probably would see a warning that it is deleted before it is finished.
Here is a minimal working example:
import sys
from PyQt4 import QtGui, QtCore
import time
import random
class MyThread(QtCore.QThread):
trigger = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(MyThread, self).__init__(parent)
def setup(self, thread_no):
self.thread_no = thread_no
def run(self):
time.sleep(random.random()*5) # random sleep to imitate working
self.trigger.emit(self.thread_no)
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.text_area = QtGui.QTextBrowser()
self.thread_button = QtGui.QPushButton('Start threads')
self.thread_button.clicked.connect(self.start_threads)
central_widget = QtGui.QWidget()
central_layout = QtGui.QHBoxLayout()
central_layout.addWidget(self.text_area)
central_layout.addWidget(self.thread_button)
central_widget.setLayout(central_layout)
self.setCentralWidget(central_widget)
def start_threads(self):
self.threads = [] # this will keep a reference to threads
for i in range(10):
thread = MyThread(self) # create a thread
thread.trigger.connect(self.update_text) # connect to it's signal
thread.setup(i) # just setting up a parameter
thread.start() # start the thread
self.threads.append(thread) # keep a reference
def update_text(self, thread_no):
self.text_area.append('thread # %d finished' % thread_no)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mainwindow = Main()
mainwindow.show()
sys.exit(app.exec_())