I am trying to get the basics of QT5 threading. This is my first attempt, combined from various sources:
import sys
from time import sleep
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QGridLayout
from PyQt5.QtCore import QThread, QObject
'''
Traceback (most recent call last):
File "threads.py", line 68, in <module>
main(sys.argv)
File "threads.py", line 63, in main
window = Window()
File "threads.py", line 15, in __init__
self.initUi()
File "threads.py", line 28, in initUi
self.worker.moveToThread(self.thread)
AttributeError: 'NoneType' object has no attribute 'moveToThread'
Press any key to continue . . .
'''
class Window(QWidget):
def __init__(self):
super().__init__()
self.initUi()
self.low = 0
self.high = 100
self.show()
def initUi(self):
self.thread = QThread()
self.worker = Worker(self)
self.worker.moveToThread(self.thread)
self.thread.start()
self.button = QPushButton(
'Start long running task')
self.layout = QGridLayout()
self.layout.addWidget(self.button, 0, 0)
self.setLayout(self.layout)
def Worker(QObject):
def __init__(self, parent):
super(Worker, self).__init__(parent)
do_work()
def do_work(self):
for _ in range(20):
print('running . . .')
sleep(2)
def main(args):
app = QApplication(args)
window = Window()
sys.exit(app.exec_())
if __name__ == '__main__':
main(sys.argv)
I have included the error I get in the code snippet.
From online articles i learned that in PyQt5 I shouldn't subclass QThread.
You have 2 problems, the first is that the worker must be a class for it changes:
def Worker(QObject):
to
class Worker(QObject):
The other problem is that you must call do_work by means of the instance, ie self, for it changes:
do_work()
to:
self.do_work()
In the following part I show a complete example:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QGridLayout
from PyQt5.QtCore import QThread, QObject
class Window(QWidget):
def __init__(self):
super().__init__()
self.initUi()
self.low = 0
self.high = 100
self.show()
def initUi(self):
self.thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.do_work)
self.thread.finished.connect(self.thread.deleteLater)
self.button = QPushButton(
'Start long running task')
self.button.clicked.connect(self.thread.start)
self.layout = QGridLayout()
self.layout.addWidget(self.button, 0, 0)
self.setLayout(self.layout)
class Worker(QObject):
def __init__(self, parent=None):
QObject.__init__(self, parent=parent)
def do_work(self):
for _ in range(20):
print('running . . .')
QThread.sleep(2)
def main(args):
app = QApplication(args)
window = Window()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main(sys.argv)
Related
I use PyQt5 to create an application. I want to update the QMainWinddow after a (time-consuming) function embedded in a QThread, which is started from a QWidget, is finished. How do I address the QMainWindow from QWidget.
Here is a minimal example of the code:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QMainWindow, QVBoxLayout, QPushButton, QLineEdit
from PyQt5.QtCore import QThread, pyqtSignal
class Thread(QThread):
progressFinished = pyqtSignal(int)
def __init__(self, n):
super(Thread, self).__init__()
self.n = n
def run(self):
for i in range(10):
QThread.msleep(100)
function_class = Function()
x = function_class.function(self.n)
self.progressFinished.emit(x)
class Function():
def function(self, n):
return n*5
class Window(QWidget):
def __init__(self):
super().__init__()
layout_window = QVBoxLayout()
self.setLayout(layout_window)
self.button = QPushButton("Open", self)
layout_window.addWidget(self.button)
self.button.clicked.connect(self.open)
self.lineedit = QLineEdit()
layout_window.addWidget(self.lineedit)
def open(self):
self.loading()
def loading(self):
self.thread = Thread(n=int(self.lineedit.text()))
self.thread.progressFinished.connect(self.set_text)
self.thread.finished.connect(self.close)
self.thread.start()
def set_text(self, link):
print(link)
class MyApp(QMainWindow):
def __init__(self):
super().__init__()
self.window_width, self.window_height = 200, 200
self.setMinimumSize(self.window_width, self.window_height)
self.button = QPushButton("Open", self)
self.button.move(0, 100)
self.button.clicked.connect(self.open)
self.label = QLabel(self)
self.label.setText(str(0))
self.label.move(0, 0)
def open(self):
self.window = Window()
self.window.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
myapp = MyApp()
myapp.show()
try:
sys.exit(app.exec_())
except SystemExit:
print('Closing Window...')
I want to use the def set_text(self, link) function from the Window class to set the text of the QLabel from the MyApp class.
I have the following code but it's complaining that I cannot access the UI data from my thread. In my example code below, What is the best way I can access the userInputString value so my threading can run?
self.nameField is a PyQt QLineEdit.
QObject::setParent: Cannot set parent, new parent is in a different thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QWidget::repaint: Recursive repaint detected
import myUI
class MainUIClass(QtGui.QMainWindow, myUI.Ui_MainWindow):
def __init__(self, parent=None):
super(MainUIClass, self).__init__(parent)
self.setupUi(self)
self.startbutton.clicked.connect(self.do_work)
self.workerThread = WorkerThread()
self.connect(self.workerThread, SIGNAL("myThreading()"), self.myThreading, Qt.DirectConnection)
def do_work(self):
self.userInputString = self.nameField.Text()
self.workerThread.start()
def myThreading(self):
if userInputString is not None:
#Do something
class WorkerThread(QThread):
def __init__(self, parent=None):
super(WorkerThread, self).__init__(parent)
def run(self):
self.emit(SIGNAL("myThreading()"))
if __name__ == '__main__':
a = QtGui.QApplication(sys.argv)
app = MainUIClass()
app.show()
a.exec_()
Not sure if it's what you need but here is a working QThread exemple using Qt5
import time
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.worker_thread = WorkerThread()
self.worker_thread.job_done.connect(self.on_job_done)
self.create_ui()
def create_ui(self):
self.button = QtWidgets.QPushButton('Test', self)
self.button.clicked.connect(self.start_thread)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
def start_thread(self):
self.worker_thread.gui_text = self.button.text()
self.worker_thread.start()
def on_job_done(self, generated_str):
print("Generated string : ", generated_str)
self.button.setText(generated_str)
class WorkerThread(QtCore.QThread):
job_done = QtCore.pyqtSignal('QString')
def __init__(self, parent=None):
super(WorkerThread, self).__init__(parent)
self.gui_text = None
def do_work(self):
for i in range(0, 1000):
print(self.gui_text)
self.job_done.emit(self.gui_text + str(i))
time.sleep(0.5)
def run(self):
self.do_work()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
test = MainWindow()
test.show()
app.exec_()
I'm trying to make a UI which communicates in the background with several BLE devices.
For that I've implemented a separate thread which runs an asyncio.loop. This is necessary because I use bleak 0.9.1 to connect to the devices.
Using signals and slots to get data from the UI-thread to the worker thread works fine. However, it does not work in the other direction. As far as I know this is because the thread is busy running the loop and never stops doing that. Therefore, it cannot process the inputs from the UI-thread.
Below there is an example code which shows the problem.
Is there any way to process the input slots in the thread while running the asyncio loop?
import sys
from PyQt5.QtWidgets import QWidget, QPushButton, QApplication, QVBoxLayout
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
import asyncio
class Test_Thread(QObject):
signal_back = pyqtSignal(int)
def __init__(self,
loop: asyncio.AbstractEventLoop,
parent=None):
super(Test_Thread, self).__init__(parent)
self.text = "Task1 not configured"
self.loop = loop
self.counter = 0
#pyqtSlot(str)
def set_text_slot(self, txt):
self.text = txt
async def do_stuff1(self):
while True:
print(self.text)
await asyncio.sleep(2.0)
async def do_stuff2(self):
while True:
self.counter += 1
self.signal_back.emit(self.counter)
await asyncio.sleep(1.0)
def work(self):
#run the event loop
try:
asyncio.ensure_future(self.do_stuff1(), loop=self.loop)
asyncio.ensure_future(self.do_stuff2(), loop=self.loop)
self.loop.run_forever()
finally:
print("Disconnect...")
class Window(QWidget):
set_text_signal = pyqtSignal(str)
def __init__(self, parent=None):
super(Window, self).__init__()
self.initUi()
self.startThread()
def initUi(self):
layout = QVBoxLayout()
self.button = QPushButton('User input')
self.button.clicked.connect(self.sendtotask)
layout.addWidget(self.button)
self.setLayout(layout)
self.show()
def startThread(self):
loop = asyncio.get_event_loop()
self.asyciothread = Test_Thread(loop)
self.thread = QThread()
self.asyciothread.moveToThread(self.thread)
self.set_text_signal.connect(self.asyciothread.set_text_slot)
self.asyciothread.signal_back.connect(self.receivefromthread)
self.thread.started.connect(self.asyciothread.work)
self.thread.start()
#pyqtSlot(int)
def receivefromthread(self, number):
print(str(number))
def sendtotask(self):
self.set_text_signal.emit("Task: Configured")
if __name__ == "__main__":
app = QApplication(sys.argv)
ui = Window()
ui.show()
sys.exit(app.exec_())
It is not necessary to use threads to use asyncio with Qt since there are libraries like asyncqt and qasync that enable it:
import asyncio
import sys
from PyQt5.QtWidgets import QWidget, QPushButton, QApplication, QVBoxLayout
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot
from asyncqt import QEventLoop
# from qasync import QEventLoop
class Worker(QObject):
signal_back = pyqtSignal(int)
def __init__(self, loop: asyncio.AbstractEventLoop, parent=None):
super(Worker, self).__init__(parent)
self.text = "Task1 not configured"
self.loop = loop
self.counter = 0
#pyqtSlot(str)
def set_text_slot(self, txt):
self.text = txt
async def do_stuff1(self):
while True:
print(self.text)
await asyncio.sleep(2.0)
async def do_stuff2(self):
while True:
self.counter += 1
self.signal_back.emit(self.counter)
await asyncio.sleep(1.0)
def work(self):
asyncio.ensure_future(self.do_stuff1(), loop=self.loop)
asyncio.ensure_future(self.do_stuff2(), loop=self.loop)
class Window(QWidget):
set_text_signal = pyqtSignal(str)
def __init__(self, parent=None):
super(Window, self).__init__()
self.initUi()
self.start_task()
def initUi(self):
layout = QVBoxLayout(self)
self.button = QPushButton("User input")
self.button.clicked.connect(self.sendtotask)
layout.addWidget(self.button)
def start_task(self):
loop = asyncio.get_event_loop()
self.worker = Worker(loop)
self.set_text_signal.connect(self.worker.set_text_slot)
self.worker.signal_back.connect(self.receive_from_worker)
self.worker.work()
#pyqtSlot(int)
def receive_from_worker(self, number):
print(str(number))
def sendtotask(self):
self.set_text_signal.emit("Task: Configured")
if __name__ == "__main__":
app = QApplication(sys.argv)
loop = QEventLoop(app)
asyncio.set_event_loop(loop)
ui = Window()
ui.show()
with loop:
loop.run_forever()
Hi in my application I have main windows and when a data comes from another thread, I need to show it in another screen for 2 seconds and then go back to previous screen. Screens has many components so I made a simple version to demonstrate my purpose.
Data comes from another thread successfully I can change the text of label. However I can not make disappear the old one and apeear the new one.
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QApplication,QMainWindow, QLabel, QWidget, QGridLayout, QVBoxLayout, QGroupBox
from PyQt5.QtGui import QTextDocument
from PyQt5 import QtCore, Qt
from PyQt5.QtGui import QIcon, QPixmap, QFont
from time import strftime
import datetime
from babel.dates import format_date, format_datetime, format_time
import sys
import worker
import time
class Form(QWidget):
def __init__(self):
super().__init__()
self.label_main = QLabel("Welcome")
self.label_uid = QLabel("Exit")
self.left = 0
self.top = 0
self._width = 480
self._height = 800
self.layout_main = QVBoxLayout()
self.layout_access = QVBoxLayout()
self.obj = worker.Worker() # no parent!
self.thread = QThread() # no parent!
self.obj.return_uid.connect(self.onCardRead)
self.obj.moveToThread(self.thread)
self.obj.finished.connect(self.thread.quit)
self.thread.started.connect(self.obj.get_uid)
self.thread.start()
self.initUI()
def initUI(self):
self.setLayout(self.layout_main)
self.layout_main.addWidget(self.label_main)
self.setWindowTitle('Main Thread')
self.show()
def secondUI(self):
self.setLayout(self.layout_access)
self.layout_access.addWidget(self.label_uid)
self.setWindowTitle('Access Thread')
self.show()
add self.windowname.close() or just self.close() after the show()
Try it:
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class Worker(QtCore.QObject):
return_uid = QtCore.pyqtSignal(int)
finished = QtCore.pyqtSignal()
def get_uid(self):
print("start")
count = 0
QtCore.QThread.msleep(1000)
while count < 10:
QtCore.QThread.msleep(200)
self.return_uid.emit(count)
count += 1
self.finished.emit()
class Form(QWidget):
def __init__(self):
super().__init__()
self.label_main = QLabel("Welcome")
self.label_uid = QLabel("Exit")
self.layout_main = QVBoxLayout()
self.layout_access = QVBoxLayout()
# self.obj = worker.Worker() # no parent!
self.obj = Worker()
self.thread = QThread()
self.obj.return_uid.connect(self.onCardRead)
self.obj.moveToThread(self.thread)
# self.obj.finished.connect(self.thread.quit)
self.obj.finished.connect(self.close)
self.thread.started.connect(self.obj.get_uid)
self.thread.start()
self.initUI()
def initUI(self):
self.setLayout(self.layout_main)
self.layout_main.addWidget(self.label_main)
self.setWindowTitle('Main Thread')
self.show()
def secondUI(self):
self.setLayout(self.layout_access)
self.layout_access.addWidget(self.label_uid)
self.setWindowTitle('Access Thread')
self.show()
def onCardRead(self, id):
self.label_main.setNum(id)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Form()
sys.exit(app.exec_())
Update
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class Worker(QtCore.QObject):
return_uid = QtCore.pyqtSignal(int)
finished = QtCore.pyqtSignal()
def get_uid(self):
print("start")
count = 0
QtCore.QThread.msleep(1000)
while count < 10:
QtCore.QThread.msleep(500)
self.return_uid.emit(count)
count += 1
self.finished.emit()
class Form(QWidget):
def __init__(self):
super().__init__()
self.label_main = QLabel("Welcome")
self.label_uid = QLabel("Exit")
self.layout_main = QVBoxLayout()
# self.layout_access = QVBoxLayout()
self.obj = Worker()
self.thread = QThread()
self.obj.return_uid.connect(self.onCardRead)
self.obj.moveToThread(self.thread)
# self.obj.finished.connect(self.close)
self.obj.finished.connect(self.secondUI) # <---
self.thread.started.connect(self.obj.get_uid)
self.thread.start()
self.initUI()
def initUI(self):
self.setLayout(self.layout_main)
self.layout_main.addWidget(self.label_main)
self.setWindowTitle('Main Thread')
self.resize(300, 100)
self.show()
def secondUI(self): # <---
self.hide()
self.windowSecond = QWidget()
self.layout_access = QVBoxLayout(self.windowSecond)
self.layout_access.addWidget(self.label_uid)
self.windowSecond.setWindowTitle('Main Screen')
self.windowSecond.resize(300, 200)
self.windowSecond.show()
def onCardRead(self, id):
self.label_main.setNum(id)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Form()
sys.exit(app.exec_())
I want to make a progressbar which runs on a thread and I want to be able to move the widget during the process:
import sys
from PyQt4.QtGui import QApplication, QMainWindow, QPushButton, QLineEdit, QLabel, QComboBox, QProgressBar, QFileDialog
from PyQt4.QtCore import QSize, pyqtSlot, QCoreApplication, SIGNAL, QThread
class App(QMainWindow):
def __init__(self):
super(App, self).__init__()
self.setGeometry(500, 300, 820, 350)
self.setWindowTitle("Program")
self.initUI()
def initUI(self):
#Buttons
btnposx = 30
btnposy = 50
self.btn4 = QPushButton('Load', self)
self.btn4.move(btnposx,btnposy+220)
self.connect(self.btn4, SIGNAL("released()"), self.test)
#ProgressBar
self.pb = QProgressBar(self)
self.pb.move(btnposx+150,btnposy+220)
self.pb.resize(470,27)
self.show()
def load(self, val):
self.pb.setValue(val)
def test(self):
self.workThread = WorkThread()
self.connect( self.workThread, SIGNAL('pb_update'), self.load)
self.workThread.start()
class WorkThread(QThread):
def __init__(self):
super(WorkThread, self).__init__()
QThread.__init__(self)
def __del__(self):
self.wait()
#pyqtSlot()
def run(self):
val = 0
l = range(1000000)
for i in l:
if i < len(l):
val += 100/len(l)
self.emit(SIGNAL('pb_update'), val)
return
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
ex.show()
sys.exit(app.exec_())
So far this works, but it does very poorly. The Widget does barely run on my machine, when I try to move it during the process. Is there a way to make this work better so that the Widget doesn't lag or stop responding?
The improvements that your code can have are the following:
Use the new connection style between signals and slots
You must leave a little time for the secondary thread to send the information to the primary thread.
You must indicate the type of connection, in your case Qt::QueuedConnection.
Use pyqtSlot decorator.
You only have to emit the signal when it is necessary, in your case when the whole value of the value changes since the QProgressBar does not recognize floating.
import sys
from PyQt4.QtGui import QApplication, QMainWindow, QPushButton, QLineEdit, QLabel, QComboBox, QProgressBar, QFileDialog
from PyQt4.QtCore import QSize, pyqtSlot, pyqtSignal, QThread, Qt
class App(QMainWindow):
def __init__(self):
super(App, self).__init__()
self.setGeometry(500, 300, 820, 350)
self.setWindowTitle("Program")
self.initUI()
def initUI(self):
#Buttons
btnposx = 30
btnposy = 50
self.btn4 = QPushButton('Load', self)
self.btn4.move(btnposx,btnposy+220)
self.btn4.released.connect(self.test)
#ProgressBar
self.pb = QProgressBar(self)
self.pb.move(btnposx+150,btnposy+220)
self.pb.resize(470,27)
self.show()
#pyqtSlot(int)
def load(self, val):
self.pb.setValue(val)
def test(self):
self.workThread = WorkThread()
self.workThread.pb_update.connect(self.load, Qt.QueuedConnection)
#self.workThread.pb_update.connect(self.pb.setValue)
self.workThread.start()
class WorkThread(QThread):
pb_update = pyqtSignal(float)
def __init__(self, *args, **kwargs):
QThread.__init__(self, *args, **kwargs)
self.value = 0
def __del__(self):
self.wait()
#pyqtSlot()
def run(self):
val = 0
l = range(1000000)
for i in l:
if i < len(l):
val += 100/len(l)
int_val = int(val)
if int_val != self.value:
self.value = int_val
self.pb_update.emit(self.value)
QThread.msleep(1)
return
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
ex.show()
sys.exit(app.exec_())