How to make a QCompleter object run on a worker thread - python

I wish to implement several autocompleters where the data passed to QCompleter is huge (63000 items), and therefore freezes the main thread. I read the QCompleter class docs but didn't find any reference to the question title, and setMaxVisibleItems isn't really what I look for, since the whole search is carried out anyway. Alternatively, limiting the search in some way to a fixed number of items as a workaround could be a valid option (also no reference to that in the docs). Maybe there is a simpler way I haven't noticed?
Update
main.py
import sys
from PyQt5 import QtCore, QtWidgets as qtw
from itertools import chain
import csv
import traceback
class WorkerSignals(QtCore.QObject):
finished = QtCore.pyqtSignal()
error = QtCore.pyqtSignal(tuple)
result = QtCore.pyqtSignal(object)
progress = QtCore.pyqtSignal(int)
class Worker(QtCore.QRunnable):
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
#QtCore.pyqtSlot()
def run(self):
try:
result = self.fn(*self.args, **self.kwargs)
except:
traceback.print_exc()
exctype, value = sys.exc_info()[:2]
self.signals.error.emit((exctype, value, traceback.format_exc()))
else:
self.signals.result.emit(result)
finally:
self.signals.finished.emit()
class MainWindow(qtw.QWidget):
def __init__(self):
super().__init__()
self.setLayout(qtw.QVBoxLayout())
self.searchBox = qtw.QLineEdit()
self.layout().addWidget(self.searchBox)
path = r"components.csv"
with open(path, "r") as f:
self.data = list(chain.from_iterable(csv.reader(f)))
self.data.sort()
self.threadpool = QtCore.QThreadPool()
# can't have completer on another thread
# self.worker_fn(self.create_completer)
self.create_completer()
self.show()
def worker_fn(self, fn):
worker = Worker(fn)
self.threadpool.start(worker)
def create_completer(self):
completer = qtw.QCompleter(self.data)
# case sensitive by default
completer.setModelSorting(qtw.QCompleter.CaseSensitivelySortedModel)
self.searchBox.setCompleter(completer)
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
w = MainWindow()
sys.exit(app.exec())
components.csv
components.csv

Related

QThread is not emitting signal even after run() finished

When User clicks on "Update" Push Button, box_updatetool_fcn will be executed. MainThread will update the Progressbar in the QMainWindow while the other QThread will download the file. But it executes perfectly, but the problem is signal emited by the WorkerThread is not updating immediately.
Even I have gone through many questions, none solved my problem. I don't know why my fcn_qthread_output is not executing immediately after Qthread finished.
Even the QThread finished() function also executing after the Main Function finished, which I am not using in my current program. I don't know what's wrong in the program, is something missing?
Here is the following console output -
Run Function Closed
Out1 : False, FileName
Main Function Ended
Out2 : True, FileName
What I am expecting is -
Run Function Closed
Out2 : True, FileName
Out1 : True, FileName
Main Function Ended
Below is the program execution flow -
class WorkerThread(QtCore.QThread):
outResult = QtCore.pyqtSignal(tuple)
def __init__(self,target,args,parent=None):
super(WorkerThread, self).__init__(parent)
self.fcn = target
self.args = args
def run(self):
outResult = self.fcn(*self.args)
self.outResult.emit(outResult)
print('Run Function Closed')
class ApplicationWindow(QtWidgets.QMainWindow):
def box_updatetool_fcn(self):
t1 = WorkerThread(target=self.boxapi.fcn_downloadfile, args=(fileID,)) #ThreadWithResult
t1.outResult.connect(self.fcn_qthread_output)
t1.start()
self.box_pbar_fcn(tempfilename,fsize)
print(f'Out1 : {self.var_box_output}')
self.boxapi.fcn_updatetool(file_fullpath,self.progpath)
print('Main Function Ended')
def fcn_qthread_output(self,result):
self.var_box_output = result
print(f'Out2 : {self.var_box_output}')
def box_pbar_fcn(self,filename,fullsize):
pobj = self.ui.progressbar
while(barprogress < 100):
if os.path.exists(filename):
currfilesize = os.path.getsize(filename)
barprogress = int(currfilesize/fullsize*100)
pobj.setValue(barprogress)
Reproducible Code -
I have used box_pbar_fcn (progress bar which shows the downloading progress) and t1 Thread (downloading the file) will Run in simultaneously.
import os, sys
from PyQt5 import QtWidgets, QtGui, QtCore
class WorkerThread(QtCore.QThread):
outResult = QtCore.pyqtSignal(tuple)
def __init__(self,parent=None):
super(WorkerThread, self).__init__(parent)
def run(self):
outResult = (True,'Out1')
self.outResult.emit(outResult)
print('Run Function Closed')
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.var_box_output = (False,'Inp1')
self.box_updatetool_fcn()
def box_updatetool_fcn(self):
tempfilename,fsize = 'Test',300
t1 = WorkerThread() #ThreadWithResult
t1.outResult.connect(self.fcn_qthread_output)
t1.start()
self.box_pbar_fcn(tempfilename,fsize)
print(f'Out1 : {self.var_box_output}')
print('Main Function Ended')
def fcn_qthread_output(self,result):
self.var_box_output = result
print(f'Out2 : {self.var_box_output}')
def box_pbar_fcn(self,filename,fullsize):
pass
#pobj = self.ui.progressbar
#while(barprogress < 100):
#if os.path.exists(filename):
# currfilesize = os.path.getsize(filename)
# barprogress = int(currfilesize/fullsize*100)
# pobj.setValue(barprogress)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mw = ApplicationWindow()
mw.show()
sys.exit(app.exec_())
The problem is that you are blocking the main thread with your while True (There should never be an instruction that blocks the eventloop for long). In Qt the logic is to do the job asynchronously. In this case I suspect that in a thread you are downloading a file and you want to show the download progress in the QProgressBar and for this you are continuously measuring the size of the file. If so, a possible solution is to use a QTimer to run the periodic task of measuring the file size.
Disclaimer: The following code that has not been tested should work. As I do not know how is the way you save the file, it could be that it does not work since many functions only write the file at the end and not in parts for efficiency reasons.
import os
import sys
from functools import cached_property
from PyQt5 import QtCore, QtGui, QtWidgets
class Downloader(QtCore.QThread):
outResult = QtCore.pyqtSignal(tuple)
def run(self):
import time
time.sleep(10)
outResult = (True, "Out1")
self.outResult.emit(outResult)
print("Run Function Closed")
class FileObserver(QtCore.QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._filename = ""
self._current_size = 0
#property
def filename(self):
return self._filename
def start(self, filename, interval=100):
self._current_size = 0
self._filename = filename
self.timer.setInterval(interval)
self.timer.start()
def stop(self):
self.timer.stop()
#cached_property
def timer(self):
timer = QtCore.QTimer()
timer.timeout.connect(self._measure)
return timer
def _measure(self):
if os.path.exists(self.filename):
file_size = os.path.getsize(filename)
if self._current_size != file_size:
self._current_size = file_size
self.file_size_changed.emit(file_size)
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.var_box_output = (False, "Inp1")
self.box_updatetool_fcn()
#cached_property
def file_observer(self):
return FileObserver()
def box_updatetool_fcn(self):
tempfilename, fullsize = "Test", 300
self.ui.progressbar.setMaximum(fullsize)
t1 = WorkerThread(self)
t1.outResult.connect(self.fcn_qthread_output)
t1.finished.connect(self.file_observer.stop)
t1.start()
self.file_observer.start(tempfilename, interval=10)
def fcn_qthread_output(self, result):
self.var_box_output = result
print(f"Out2 : {self.var_box_output}")
def box_pbar_fcn(self, file_size):
self.ui.progressbar.setValue(file_size)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mw = ApplicationWindow()
mw.show()
sys.exit(app.exec_())

How to update UI with output from QProcess loop without the UI freezing?

I am wanting to have a list of commands being processed through a QProcess and have its output be appended to a textfield I have. I've found a these two pages that seems to do each of the things i need (updating the UI, and not freezing the UI via QThread):
Printing QProcess Stdout only if it contains a Substring
https://nikolak.com/pyqt-threading-tutorial/
So i tried to combine these two....
import sys
from PySide import QtGui, QtCore
class commandThread(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
self.cmdList = None
self.process = QtCore.QProcess()
def __del__(self):
self.wait()
def command(self):
# print 'something'
self.process.start('ping', ['127.0.0.1'])
processStdout = str(self.process.readAll())
return processStdout
def run(self):
for i in range(3):
messages = self.command()
self.emit(QtCore.SIGNAL('dataReady(QString)'), messages)
# self.sleep(1)
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.initUI()
def dataReady(self,outputMessage):
cursorOutput = self.output.textCursor()
cursorSummary = self.summary.textCursor()
cursorOutput.movePosition(cursorOutput.End)
cursorSummary.movePosition(cursorSummary.End)
# Update self.output
cursorOutput.insertText(outputMessage)
# Update self.summary
for line in outputMessage.split("\n"):
if 'TTL' in line:
cursorSummary.insertText(line)
self.output.ensureCursorVisible()
self.summary.ensureCursorVisible()
def initUI(self):
layout = QtGui.QHBoxLayout()
self.runBtn = QtGui.QPushButton('Run')
self.runBtn.clicked.connect(self.callThread)
self.output = QtGui.QTextEdit()
self.summary = QtGui.QTextEdit()
layout.addWidget(self.runBtn)
layout.addWidget(self.output)
layout.addWidget(self.summary)
centralWidget = QtGui.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
# self.process.started.connect(lambda: self.runBtn.setEnabled(False))
# self.process.finished.connect(lambda: self.runBtn.setEnabled(True))
def callThread(self):
self.runBtn.setEnabled(False)
self.get_thread = commandThread()
# print 'this this running?'
self.connect(self.get_thread, QtCore.SIGNAL("dataReady(QString)"), self.dataReady)
self.connect(self.get_thread, QtCore.SIGNAL("finished()"), self.done)
def done(self):
self.runBtn.setEnabled(True)
def main():
app = QtGui.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The problem is that once I click the "Run" button the textfield on the right doesn't seem to populate, and i am no longer getting any errors so I am not sure what is happening.
I tried referring to this page as well but I think i am already emulating what it is describing...?
https://www.qtcentre.org/threads/46056-QProcess-in-a-loop-works-but
Ultimately what I want to build is for a main window to submit a series of commands via subprocess/QProcess, and open up a little log window that constantly updates it on the progress via displaying the console output. Similar to what you kind of see in like Installer packages...
I feel like i am so close to an answer, yet so far away. Is anyone able to chime in on this?
EDIT: so to answer eyllanesc's question, the list of commands has to be run one after the previous one has completed, as the command i plan to use will be very CPU intensive, and i cannot have more than one process of it running. also the time of each command completing will completely vary so I can't just have a arbitrary hold like with time.sleep() as some may complete quicker/slower than others. so ideally figuring out when the process has finished should kickstart another command (which is why i have a for loop in this example to represent that).
i also decided to use threads because apparently that was one way of preventing the UI to freeze while the process was running,so i assumed i needed to utilize this to have a sort of live feed/update in the text field.
the other thing is in the UI i would ideally in addition to updating a text field with console logs, i would want it to have some sort of label that gets updated that says something like "2 of 10 jobs completed". so something like this:
It would be nice too when before a new command is being processed a custom message can be appended to the text field indicating what command is being run...
UPDATE: apologies for taking so long to post an update on this, but based on eyllanesc's answer, I was able to figure out how to make this open a separate window and run the "ping" commands. here is the example code I have made to achieve my results in my main application:
from PySide import QtCore, QtGui
class Task:
def __init__(self, program, args=None):
self._program = program
self._args = args or []
#property
def program(self):
return self._program
#property
def args(self):
return self._args
class SequentialManager(QtCore.QObject):
started = QtCore.Signal()
finished = QtCore.Signal()
progressChanged = QtCore.Signal(int)
dataChanged = QtCore.Signal(str)
#^ this is how we can send a signal and can declare what type
# of information we want to pass with this signal
def __init__(self, parent=None):
super(SequentialManager, self).__init__(parent)
self._progress = 0
self._tasks = []
self._process = QtCore.QProcess(self)
self._process.setProcessChannelMode(QtCore.QProcess.MergedChannels)
self._process.finished.connect(self._on_finished)
self._process.readyReadStandardOutput.connect(self._on_readyReadStandardOutput)
def execute(self, tasks):
self._tasks = iter(tasks)
#this 'iter()' method creates an iterator object
self.started.emit()
self._progress = 0
self.progressChanged.emit(self._progress)
self._execute_next()
def _execute_next(self):
try:
task = next(self._tasks)
except StopIteration:
return False
else:
self._process.start(task.program, task.args)
return True
# QtCore.Slot()
#^ we don't need this line here
def _on_finished(self):
self._process_task()
if not self._execute_next():
self.finished.emit()
# #QtCore.Slot()
def _on_readyReadStandardOutput(self):
output = self._process.readAllStandardOutput()
result = output.data().decode()
self.dataChanged.emit(result)
def _process_task(self):
self._progress += 1
self.progressChanged.emit(self._progress)
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.outputWindow = outputLog(parentWindow=self)
self._button = QtGui.QPushButton("Start")
central_widget = QtGui.QWidget()
lay = QtGui.QVBoxLayout(central_widget)
lay.addWidget(self._button)
self.setCentralWidget(central_widget)
self._button.clicked.connect(self.showOutput)
def showOutput(self):
self.outputWindow.show()
self.outputWindow.startProcess()
#property
def startButton(self):
return self._button
class outputLog(QtGui.QWidget):
def __init__(self, parent=None, parentWindow=None):
QtGui.QWidget.__init__(self,parent)
self.parentWindow = parentWindow
self.setWindowTitle('Render Log')
self.setMinimumSize(225, 150)
self.renderLogWidget = QtGui.QWidget()
lay = QtGui.QVBoxLayout(self.renderLogWidget)
self._textedit = QtGui.QTextEdit(readOnly=True)
self._progressbar = QtGui.QProgressBar()
self._button = QtGui.QPushButton("Close")
self._button.clicked.connect(self.windowClose)
lay.addWidget(self._textedit)
lay.addWidget(self._progressbar)
lay.addWidget(self._button)
self._manager = SequentialManager(self)
self.setLayout(lay)
def startProcess(self):
self._manager.progressChanged.connect(self._progressbar.setValue)
self._manager.dataChanged.connect(self.on_dataChanged)
self._manager.started.connect(self.on_started)
self._manager.finished.connect(self.on_finished)
self._progressbar.setFormat("%v/%m")
self._progressbar.setValue(0)
tasks = [
Task("ping", ["8.8.8.8"]),
Task("ping", ["8.8.8.8"]),
Task("ping", ["8.8.8.8"]),
Task("ping", ["8.8.8.8"]),
Task("ping", ["8.8.8.8"]),
Task("ping", ["8.8.8.8"]),
]
self._progressbar.setMaximum(len(tasks))
self._manager.execute(tasks)
#QtCore.Slot()
def on_started(self):
self._button.setEnabled(False)
self.parentWindow.startButton.setEnabled(False)
#QtCore.Slot()
def on_finished(self):
self._button.setEnabled(True)
#QtCore.Slot(str)
def on_dataChanged(self, message):
if message:
cursor = self._textedit.textCursor()
cursor.movePosition(QtGui.QTextCursor.End)
cursor.insertText(message)
self._textedit.ensureCursorVisible()
def windowClose(self):
self.parentWindow.startButton.setEnabled(True)
self.close()
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
i still don't really understand the use of the QtCore.Slot() decorators as when I commented them out it didn't really seem to change the result. But i kept them in just to be safe.
It is not necessary to use threads in this case since QProcess is executed using the event loop. The procedure is to launch a task, wait for the finishes signal, get the result, send the result, and execute the next task until all the tasks are finished. The key to the solution is to use the signals and distribute the tasks with an iterator.
Considering the above, the solution is:
from PySide import QtCore, QtGui
class Task:
def __init__(self, program, args=None):
self._program = program
self._args = args or []
#property
def program(self):
return self._program
#property
def args(self):
return self._args
class SequentialManager(QtCore.QObject):
started = QtCore.Signal()
finished = QtCore.Signal()
progressChanged = QtCore.Signal(int)
dataChanged = QtCore.Signal(str)
def __init__(self, parent=None):
super(SequentialManager, self).__init__(parent)
self._progress = 0
self._tasks = []
self._process = QtCore.QProcess(self)
self._process.setProcessChannelMode(QtCore.QProcess.MergedChannels)
self._process.finished.connect(self._on_finished)
self._process.readyReadStandardOutput.connect(self._on_readyReadStandardOutput)
def execute(self, tasks):
self._tasks = iter(tasks)
self.started.emit()
self._progress = 0
self.progressChanged.emit(self._progress)
self._execute_next()
def _execute_next(self):
try:
task = next(self._tasks)
except StopIteration:
return False
else:
self._process.start(task.program, task.args)
return True
QtCore.Slot()
def _on_finished(self):
self._process_task()
if not self._execute_next():
self.finished.emit()
#QtCore.Slot()
def _on_readyReadStandardOutput(self):
output = self._process.readAllStandardOutput()
result = output.data().decode()
self.dataChanged.emit(result)
def _process_task(self):
self._progress += 1
self.progressChanged.emit(self._progress)
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self._button = QtGui.QPushButton("Start")
self._textedit = QtGui.QTextEdit(readOnly=True)
self._progressbar = QtGui.QProgressBar()
central_widget = QtGui.QWidget()
lay = QtGui.QVBoxLayout(central_widget)
lay.addWidget(self._button)
lay.addWidget(self._textedit)
lay.addWidget(self._progressbar)
self.setCentralWidget(central_widget)
self._manager = SequentialManager(self)
self._manager.progressChanged.connect(self._progressbar.setValue)
self._manager.dataChanged.connect(self.on_dataChanged)
self._manager.started.connect(self.on_started)
self._manager.finished.connect(self.on_finished)
self._button.clicked.connect(self.on_clicked)
#QtCore.Slot()
def on_clicked(self):
self._progressbar.setFormat("%v/%m")
self._progressbar.setValue(0)
tasks = [
Task("ping", ["8.8.8.8"]),
Task("ping", ["8.8.8.8"]),
Task("ping", ["8.8.8.8"]),
]
self._progressbar.setMaximum(len(tasks))
self._manager.execute(tasks)
#QtCore.Slot()
def on_started(self):
self._button.setEnabled(False)
#QtCore.Slot()
def on_finished(self):
self._button.setEnabled(True)
#QtCore.Slot(str)
def on_dataChanged(self, message):
if message:
cursor = self._textedit.textCursor()
cursor.movePosition(QtGui.QTextCursor.End)
cursor.insertText(message)
self._textedit.ensureCursorVisible()
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())

QWidget not loading child elements when QRunnable is also called

I'm working in a data processing desktop application with Python 3.7 and PySide2 on which requires me to load data from several large (approx 250k rows) excel files into the program's processing library. For this I've set up in my application a simple popup (called LoadingPopup) which includes a rotating gif and a simple caption, and also some code that loads the data from the excel files into a global object using pandas. Both of these things work as intended when run on their own, but if I happen to create a loading dialog and a QRunnable worker in the same scope of my codebase, the widgets contained in loading widget (a gif and a simple caption) will simply not show.
I've tried changing the parent type for my widget from QDialog to QWidget, or initializing the popup (the start() function) both outside and inside the widget. I'm not very experienced with Qt5 so I don't know what else to do.
import sys, time, traceback
from PySide2.QtWidgets import *
from PySide2.QtCore import *
from PySide2.QtGui import *
from TsrUtils import PathUtils
class WorkerSignals(QObject):
finished = Signal()
error = Signal(tuple)
result = Signal(object)
class TsrWorker(QRunnable):
def __init__(self, fn, *args, **kwargs):
super(TsrWorker, self).__init__()
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
#Slot()
def run(self):
try:
result = self.fn(*self.args, **self.kwargs)
except:
traceback.print_exc()
exctype, value = sys.exc_info()[:2]
self.signals.error.emit((exctype, value, traceback.format_exc()))
else:
self.signals.result.emit(result)
finally:
self.signals.finished.emit()
class LoadingPopup(QWidget):
def __init__(self):
super().__init__()
self.message = "Cargando"
self.setMinimumSize(350,300)
self.setWindowIcon(\
QIcon(PathUtils.ruta_absoluta('resources/icons/tsr.png')))
self.setWindowTitle(self.message)
self.layout = QVBoxLayout(self)
self.setLayout(self.layout)
self.movie = QMovie(self)
self.movie.setFileName("./resources/img/spinner.gif")
self.movie.setCacheMode(QMovie.CacheAll)
self.movie.start()
self.loading = QLabel(self)
self.loading.setMovie(self.movie)
self.loading.setAlignment(Qt.AlignCenter)
self.layout.addWidget(self.loading)
self.lbl = QLabel(self.message, self)
self.lbl.setAlignment(Qt.AlignCenter)
self.lbl.setStyleSheet("font: 15pt")
self.layout.addWidget(self.lbl)
class MyMainApp(QApplication):
def __init__(self, args):
super().__init__()
self.l = LoadingPopup()
self.l.show()
w = TsrWorker(time.sleep, 5)
w.signals.finished.connect(self.terminado)
w.run()
def terminado(self):
print('timer finished')
self.l.hide()
if __name__ == '__main__':
app = MyMainApp(sys.argv)
sys.exit(app.exec_())
I've changed the actual data loading part of the application in the example with a time.sleep function. MY expected results are that I should be able to have the LoadingPopup show up with a gif moving, and then it should close once the QRunnable finishes.
You should not call the run method directly since you will have the heavy task run on the GUI thread freezing it. You must launch it using QThreadPool:
class MyMainApp(QApplication):
def __init__(self, args):
super().__init__()
self.l = LoadingPopup()
self.l.show()
w = TsrWorker(time.sleep, 5)
w.signals.finished.connect(self.terminado)
# w.run()
QThreadPool.globalInstance().start(w) # <---
def terminado(self):
print('timer finished')
self.l.hide()

using threading for a qt application

I have the following two files:
import sys
import time
from PyQt4 import QtGui, QtCore
import btnModule
class WindowClass(QtGui.QWidget):
def __init__(self):
super(WindowClass, self).__init__()
self.dataLoaded = None
# Widgets
# Buttons
thread = WorkerForLoop(self.runLoop)
# thread.start()
self.playBtn = btnModule.playpauselBtnClass \
('Play', thread.start)
# Layout
layout = QtGui.QHBoxLayout()
layout.addWidget(self.playBtn)
self.setLayout(layout)
# Window Geometry
self.setGeometry(100, 100, 100, 100)
def waitToContinue(self):
print self.playBtn.text()
while (self.playBtn.text() != 'Pause'):
pass
def runLoop(self):
for ii in range(100):
self.waitToContinue()
print 'num so far: ', ii
time.sleep(0.5)
class WorkerForLoop(QtCore.QThread):
def __init__(self, function, *args, **kwargs):
super(WorkerForLoop, self).__init__()
self.function = function
self.args = args
self.kwargs = kwargs
def __del__(self):
self.wait()
def run(self):
print 'let"s run it'
self.function(*self.args, **self.kwargs)
return
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
wmain = WindowClass()
wmain.show()
sys.exit(app.exec_())
and the second file btnModule.py:
from PyQt4 import QtGui, QtCore
class playpauselBtnClass(QtGui.QPushButton):
btnSgn = QtCore.pyqtSignal()
def __init__(self, btnName, onClickFunction):
super(playpauselBtnClass, self).__init__(btnName)
self.clicked.connect(self.btnPressed)
self.btnSgn.connect(onClickFunction)
def btnPressed(self):
if self.text() == 'Play':
self.setText('Pause')
self.btnSgn.emit()
print 'Changed to pause and emited signal'
elif self.text() == 'Pause':
self.setText('Continue')
print 'Changed to Continue'
elif self.text() == 'Continue':
self.setText('Pause')
print 'Changed to Pause'
If in the first file I remove the comment at thread.start() it works as expected, it starts the thread and then hangs until I click Play on the UI. However, I thought it should work even if I didn't start it there as the signal is btnSgn is connected to onClickFunction which in this case takes the value thread.start.
Used this as a reference
http://joplaete.wordpress.com/2010/07/21/threading-with-pyqt4/
I think the reason it doesn't work when you try to call thread.start() in your second file (via self.btnSgn.emit()) is that the thread object goes out of scope outside of the init function where you created it. So you are calling start() on an already deleted thread.
Just changing thread -> self.thread (i.e. making the thread object a member of the WindowClass object) works fine when I tried it, since thread is then kept alive till the end of the program.

PySide: Easier way of updating GUI from another thread

I have a PySide (Qt) GUI which spawns multiple threads. The threads sometimes need to update the GUI. I have solved this in the following way:
class Signaller(QtCore.QObject) :
my_signal = QtCore.Signal(QListWidgetItem, QIcon)
signaller = Signaller()
class MyThread(threading.Thread):
def __init__(self):
super(IconThread, self).__init__()
# ...
def run(self) :
# ...
# Need to update the GUI
signaller.my_signal.emit(self.item, icon)
#
# MAIN WINDOW
#
class Main(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
# ...
# Connect signals
signaller.my_signal.connect(self.my_handler)
#QtCore.Slot(QListWidgetItem, QIcon)
def my_handler(self, item, icon):
item.setIcon(icon)
def do_something(self, address):
# ...
# Start new thread
my_thread = MyThread(newItem)
my_thread.start()
# ...
Is there an easier way? Creating the signals, handlers and connect them requires a few lines of code.
I started coding with PySide recently and I needed a equivalent of PyGObject's GLib.idle_add behaviour. I based the code off of your answer ( https://stackoverflow.com/a/11005204/1524507 ) but this one uses events instead of using a queue ourselves.
from PySide import QtCore
class InvokeEvent(QtCore.QEvent):
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
def __init__(self, fn, *args, **kwargs):
QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
self.fn = fn
self.args = args
self.kwargs = kwargs
class Invoker(QtCore.QObject):
def event(self, event):
event.fn(*event.args, **event.kwargs)
return True
_invoker = Invoker()
def invoke_in_main_thread(fn, *args, **kwargs):
QtCore.QCoreApplication.postEvent(_invoker,
InvokeEvent(fn, *args, **kwargs))
Which is used the same way in the above answer link.
This is what I have so far. I wrote the following code somewhere in a helper module:
from Queue import Queue
class Invoker(QObject):
def __init__(self):
super(Invoker, self).__init__()
self.queue = Queue()
def invoke(self, func, *args):
f = lambda: func(*args)
self.queue.put(f)
QMetaObject.invokeMethod(self, "handler", QtCore.Qt.QueuedConnection)
#Slot()
def handler(self):
f = self.queue.get()
f()
invoker = Invoker()
def invoke_in_main_thread(func, *args):
invoker.invoke(func,*args)
Then my threads can very easily run code to update the GUI in the main thread. There is no need to create and connect signals for every operation.
class MyThread(threading.Thread):
def __init__(self):
super(IconThread, self).__init__()
# ...
def run(self) :
# ...
# Need to update the GUI
invoke_in_main_thread(self.item.setIcon, icon)
I think something like this is quite nice.

Categories