PyQt5: How to connect() & emit() - python

I am trying to port code from PyQt4 to PyQt5 and am not understanding why the following does not work. The slot is not being called. I see a bunch of ticks and no tocks. What am I missing?
from PyQt5 import QtCore
import time
# expect to see ticks & tocks
class Alarm(QtCore.QThread, QtCore.QObject):
signal = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(Alarm, self).__init__(parent)
self.signal.connect(self.eventp)
self.start()
def run(self):
while True:
print('tick')
self.signal.emit()
time.sleep(1)
#QtCore.pyqtSlot()
def eventp(self):
print('Tock')
# main
alarm = Alarm()
time.sleep(6) # wait for countdown, then terminate

First QThread already inherits from QObject so it is not necessary to use it as an interface. On the other hand the QThread must live in an eventloop since that is what allows the transmission of the signals, for example in your case you are blocking the eventloop with the time.sleep(6), instead if you want to finish the loop after 6 seconds use a QTimer:
import time
from PyQt5 import QtCore
# expect to see ticks & tocks
class Alarm(QtCore.QThread):
signal = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(Alarm, self).__init__(parent)
self.signal.connect(self.eventp)
self.start()
def run(self):
while True:
print('tick')
self.signal.emit()
time.sleep(1)
#QtCore.pyqtSlot()
def eventp(self):
print('Tock')
if __name__ == '__main__':
import sys
app = QtCore.QCoreApplication(sys.argv)
alarm = Alarm()
QtCore.QTimer.singleShot(6*1000, QtCore.QCoreApplication.quit)
sys.exit(app.exec_())

Related

Qt for Python QCoreApplication: How to Exit non-GUI application

I have a small non-GUI application that basically starts an event loop, connects a signal to a slot, and emits a signal. I would like the slot to stop the event loop and exit the application.
However, the application does not exit.
Does anyone have any ideas on how to exit from the event loop?
Python 3.7.0
Qt for Python (PySide2) 5.12.0
import sys
from PySide2 import QtCore, QtWidgets
class ConsoleTest(QtCore.QObject):
all_work_done = QtCore.Signal(str)
def __init__(self, parent=None):
super(ConsoleTest, self).__init__(parent)
self.run_test()
def run_test(self):
self.all_work_done.connect(self.stop_test)
self.all_work_done.emit("foo!")
#QtCore.Slot(str)
def stop_test(self, msg):
print(f"Test is being stopped, message: {msg}")
# neither of the next two lines will exit the application.
QtCore.QCoreApplication.quit()
# QtCore.QCoreApplication.exit(0)
return
if __name__ == "__main__":
app = QtCore.QCoreApplication(sys.argv)
mainwindow = ConsoleTest()
sys.exit(app.exec_())
When you call app.exec_() you are entering the Qt event loop, but since you are executing the code that call quit() at the moment the object is being initialized, your application will never quit.
I can think of two options to achieve the "exit" process you want to do:
Just call sys.exit(1) inside the stop_test() instead of a quit or exit call, or
you use singleShot to quit the application:
import sys
from PySide2 import QtCore, QtWidgets
class ConsoleTest(QtCore.QObject):
all_work_done = QtCore.Signal(str)
def __init__(self, parent=None):
super(ConsoleTest, self).__init__(parent)
self.run_test()
def run_test(self):
self.all_work_done.connect(self.stop_test)
self.all_work_done.emit("foo!")
#QtCore.Slot(str)
def stop_test(self, msg):
print(f"Test is being stopped, message: {msg}")
QtCore.QTimer.singleShot(10, QtCore.qApp.quit)
if __name__ == "__main__":
app = QtCore.QCoreApplication(sys.argv)
mainwindow = ConsoleTest()
sys.exit(app.exec_())

PyQt5.QThread's start() méthod don't execute the run() méthod

im starting to learn PyQt5 and Qthread and im trying to do a simple QThread implementation, i know it's obvious but i can't really get why it dosen't work
my code :
from PyQt5 import QtCore
class WorkingThread(QtCore.QThread):
def __init__(self):
super().__init__()
def run(self):
print(" work !")
class MainWindow(QtCore.QObject):
worker_thread = WorkingThread()
def engage(self):
print("calling start")
self.worker_thread.start()
if __name__ == "__main__":
main = MainWindow()
main.engage()
the output:
calling start
Process finished with exit code 0
no "work !" printed
many of the elements of Qt need an eventloop to work correctly, and that is the case of QThread, as in this case there is no GUI it is appropriate to create a QCoreApplication:
from PyQt5 import QtCore
class WorkingThread(QtCore.QThread):
def run(self):
print(" work !")
class MainWindow(QtCore.QObject):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.worker_thread = WorkingThread()
def engage(self):
print("calling start")
self.worker_thread.start()
if __name__ == "__main__":
import sys
app = QtCore.QCoreApplication(sys.argv)
main = MainWindow()
main.engage()
sys.exit(app.exec_())

How do I properly perform multiprocessing from PyQt?

I create a button and try to run multiprocessing when I click button ,
but the UI is become blocked . I hope process run in backgorund .
How can I fix it ?
from PySide2 import QtCore,QtGui,QtWidgets
import sys
import multiprocessing
from threading import Timer
class TTT(multiprocessing.Process):
def __init__(self):
super(TTT, self).__init__()
self.daemon = True
def run(self):
while True:
t = Timer(5, self.doSomething)
t.start()
t.join()
def doSomething(self):
try:
print('123')
except Exception as e:
print(e)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
btn = QtWidgets.QPushButton('run process')
btn.clicked.connect(self.create_process)
self.setCentralWidget(btn)
def create_process(self):
QtWidgets.QMessageBox.information(self,'hhh','hhh')
t = TTT()
t.start()
t.join()
if __name__=="__main__":
app=QtWidgets.QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
Bendegúz Szatmári already answer the main question.
I just want to let you know that use Process is not best idea in most of usage.
Different process does not share memory with your program. You can not control them so easily as different thread.
Here is simple example how you can Start end Stop different thread.
from PyQt5 import QtWidgets
from PyQt5.QtCore import *
import sys
import time
class TTT(QThread):
def __init__(self):
super(TTT, self).__init__()
self.quit_flag = False
def run(self):
while True:
if not self.quit_flag:
self.doSomething()
time.sleep(1)
else:
break
self.quit()
self.wait()
def doSomething(self):
print('123')
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.btn = QtWidgets.QPushButton('run process')
self.btn.clicked.connect(self.create_process)
self.setCentralWidget(self.btn)
def create_process(self):
if self.btn.text() == "run process":
print("Started")
self.btn.setText("stop process")
self.t = TTT()
self.t.start()
else:
self.t.quit_flag = True
print("Stop sent")
self.t.wait()
print("Stopped")
self.btn.setText("run process")
if __name__=="__main__":
app=QtWidgets.QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
I have never used multiprocessing, but docs says that the join() method blocks the caller until it is finished. Putting the method in an infinite loop will block the caller(the UI) forever.
Here's a nice strategy:
https://elsampsa.github.io/valkka-examples/_build/html/qt_notes.html#python-multiprocessing
Features:
You can send Qt signals from the multiprocess
You can send Qt signals into the multiprocess

PyQt5: Timer in a thread

Problem Description
I'm trying to make an application that collects data, processes it, displays it, and some actuation (open/close valves, etc). As a practice for future applications where I have some stricter time constraints, I want to run the data capture and processing in a separate thread.
My current problem is that it's telling me I cannot start a timer from another thread.
Current code progress
import sys
import PyQt5
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QThread, pyqtSignal
# This is our window from QtCreator
import mainwindow_auto
#thread to capture the process data
class DataCaptureThread(QThread):
def collectProcessData():
print ("Collecting Process Data")
#declaring the timer
dataCollectionTimer = PyQt5.QtCore.QTimer()
dataCollectionTimer.timeout.connect(collectProcessData)
def __init__(self):
QThread.__init__(self)
def run(self):
self.dataCollectionTimer.start(1000);
class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self) # gets defined in the UI file
self.btnStart.clicked.connect(self.pressedStartBtn)
self.btnStop.clicked.connect(self.pressedStopBtn)
def pressedStartBtn(self):
self.lblAction.setText("STARTED")
self.dataCollectionThread = DataCaptureThread()
self.dataCollectionThread.start()
def pressedStopBtn(self):
self.lblAction.setText("STOPPED")
self.dataCollectionThread.terminate()
def main():
# a new app instance
app = QApplication(sys.argv)
form = MainWindow()
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Any advice on how to get this to work would be appreciated!
You have to move the QTimer to the DataCaptureThread thread, in addition to that when the run method ends, the thread is eliminated so the timer is eliminated, so you must avoid running that function without blocking other tasks. QEventLoop is used for this:
class DataCaptureThread(QThread):
def collectProcessData(self):
print ("Collecting Process Data")
def __init__(self, *args, **kwargs):
QThread.__init__(self, *args, **kwargs)
self.dataCollectionTimer = QTimer()
self.dataCollectionTimer.moveToThread(self)
self.dataCollectionTimer.timeout.connect(self.collectProcessData)
def run(self):
self.dataCollectionTimer.start(1000)
loop = QEventLoop()
loop.exec_()

PyQt5 GUI only updates when screen is clicked off and back on

I am running into an issue when I am taking in values over serial and then attempting to update my Gui with those values. Unfortunately, even though the values update correctly, I am unable to get to the screen to refresh unless I click off of it and then back on to it. I have tried repaint, update, and processEvents() but have been unable to solve the problem.
Here is the code I am working with:
import sys
import serial
import time
import requests
import PyQt5
from PyQt5.QtWidgets import *
from PyQt5.QtCore import*
from PyQt5.QtGui import *
import mainwindow_auto
CUSTOM_EVENT = 1000
ser = serial.Serial('/dev/ttyACM0', 9600)
class TestThread(QThread):
def __init__(self, target):
QThread.__init__(self)
self.target = target
def run(self):
while True:
QApplication.postEvent(self.target, QEvent(QEvent.Type(CUSTOM_EVENT)))
QApplication.processEvents()
QThread.sleep(15)
class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self)# gets defined in the UI file
self.thread = TestThread(self)
self.thread.start()
def event(s, e):
if(e.type() == CUSTOM_EVENT):
print("Readline: ",int(ser.readline()))
SOC = int(ser.readline())
s.lcdNumber.display(SOC)
s.progressBar.setValue(SOC)
print("SOC: ",SOC)
print(s.lcdNumber.value())
return True
def main():
app = QApplication(sys.argv)
form = MainWindow()
form.lcdNumber.display(30)
form.progressBar.setValue(30)
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Thanks in advance!
Since you already have an I/O thread, let it handle the I/O and sent the received value to the main thread via a signal.
No need for a custom event, no I/O on the main thread.
Just adding a signal to the thread subclass and connecting a slot to that before starting the thread.
Rather than rewriting the code that I had above, I ended up fixing it by force redrawing using s.hide() and s.show() after updating the values in the event code. It forced a redraw that otherwise refused to work.
s.lcdNumber.display(SOC)
s.progressBar.setValue(SOC)
s.hide()
s.show()
As suggested by #KevinKrammer, this is simple to do with a custom signal:
class TestThread(QThread):
serialUpdate = pyqtSignal(int)
def run(self):
while True:
QThread.sleep(1)
value = int(ser.readline())
self.serialUpdate.emit(value)
class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUi(self)
self.thread = TestThread(self)
self.thread.serialUpdate.connect(self.handleSerialUpdate)
self.thread.start()
def handleSerialUpdate(self, value):
print("Readline: ", value)
self.lcdNumber.display(value)
self.progressBar.setValue(value)

Categories

Resources