Updating PyQt5 GUI with Live Data [duplicate] - python

This question already has an answer here:
PyQt4 - creating a timer
(1 answer)
Closed 4 years ago.
I created a PyQt5 GUI using QtDesigner and converted it to Python. I was planning to update and display the value from the sensor reading from the Raspberry Pi. Since the GUI is in a loop, there is no way I can update data from outside that loop. Currently I used the code below where I use the QTimer widget that executes the function every given interval. Is this solution appropriate or not? What other methods are available to accomplish this task?
from PyQt5 import QtCore, QtGui, QtWidgets
from uimainwindow import Ui_MainWindow
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
numTest=0;
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.setupUi(self)
QtCore.QTimer.singleShot(1000, self.getSensorValue)
def getSensorValue(self):
print(self.numTest)
self.numTest=self.numTest+1
QtCore.QTimer.singleShot(1000, self.getSensorValue)
if __name__=="__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w=MainWindow()
w.show()
sys.exit(app.exec_())

To use a QTimer which calls a member function periodically:
Make a member variable of QTimer.
Set interval of QTimer to the intended delay.
Connect getSensorValue() as signal handler to QTimer.timeout().
Start QTimer member.
Demo test-QTimer.py:
#!/usr/bin/python3
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
from PyQt5.QtCore import QTimer
class MainWindow(QMainWindow):
# constructor
def __init__(self):
QMainWindow.__init__(self)
# counter
self.i = 0
# add QLabel
self.qLbl = QLabel('Not yet initialized')
self.setCentralWidget(self.qLbl)
# make QTimer
self.qTimer = QTimer()
# set interval to 1 s
self.qTimer.setInterval(1000) # 1000 ms = 1 s
# connect timeout signal to signal handler
self.qTimer.timeout.connect(self.getSensorValue)
# start timer
self.qTimer.start()
def getSensorValue(self):
self.i += 1
# print('%d. call of getSensorValue()' % self.i)
self.qLbl.setText('%d. call of getSensorValue()' % self.i)
qApp = QApplication(sys.argv)
# setup GUI
qWin = MainWindow()
qWin.show()
# run application
sys.exit(qApp.exec_())
Tested in cygwin on Windows 10:

Related

Python Qt - do signals need to be created in the worker thread?

I am using Python Qt (PySide2) which has a GUI in the main thread and a library that does IO, data-crunching, etc in a second thread.
I use signals & slots to update the GUI. In the examples I have seen on SO, the signal is always created in the worker (non-GUI) thread. Is it necessary to do this?
Reason: my library can be used with a GUI or could be used in another Python script. So, it might output data to the GUI or maybe to console/log file. To make the code in the library generic, I thought that whatever calls the library can register a callback. That callback can be to emit to Qt or output to file, etc.
Here's an example where the Signal is created in the GUI thread. It works, but could it cause thread-related issues?
import threading
import time
import sys
from PySide2.QtWidgets import QWidget, QVBoxLayout, QTextEdit, QPushButton, QApplication
from PySide2 import QtCore
class aio_connection(QtCore.QObject):
data_recvd = QtCore.Signal(object)
class TextEditDemo(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("TEST")
self.resize(600,540)
self.textEdit = QTextEdit()
self.btnPress1 = QPushButton("Run")
layout = QVBoxLayout()
layout.addWidget(self.textEdit)
layout.addWidget(self.btnPress1)
self.setLayout(layout)
self.btnPress1.clicked.connect(self.btnPress1_Clicked)
self.aio_conn = aio_connection() # Signal is created in main (GUI) thread
# Connect signals (data_recvd) and slots (on_data_ready)
self.aio_conn.data_recvd.connect(self.on_data_ready)
def btnPress1_Clicked(self):
threading.Thread(target=bg_task, args=(self.cb,)).start()
#QtCore.Slot()
def on_data_ready(self, msg):
self.textEdit.append(msg)
def cb(self, info):
self.aio_conn.data_recvd.emit(info)
# Simulate the library that runs in a second thread
def bg_task(callback):
for i in range(100):
callback(str(i))
time.sleep(0.1)
def main():
app = QApplication(sys.argv)
win = TextEditDemo()
win.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
By default, signal connections are made using the AutoConnection type. This means the eventual type of connection will be automatically made at runtime, when the signal is actually emitted. If the sender and receiver are in different threads, Qt will use a queued connection, which will post an event to the event-queue of the receiving thread. Thus, when control returns to the receiver, the event will be processed and any connected slots will be called at that time.
This is an important consideration when updating GUI elements from worker threads, because Qt does not support GUI-related operations of any kind outside the main thread. However, so long as you always use signals to communicate between threads using the default connection-type, you should not have any problems - Qt will automatically guarantee they are done in a thread-safe way.
Below is a version of your scripts that verifies everything is working as desired. When I run it, I get the following output, which shows the current thread-id within each function call:
main: 4973
bg_task: 4976
cb: 4976
on_data_ready: 4973
cb: 4976
on_data_ready: 4973
cb: 4976
...
import threading, time, sys
from PySide2.QtWidgets import QWidget, QVBoxLayout, QTextEdit, QPushButton, QApplication
from PySide2 import QtCore
class aio_connection(QtCore.QObject):
data_recvd = QtCore.Signal(object)
class TextEditDemo(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("TEST")
self.resize(600,540)
self.textEdit = QTextEdit()
self.btnPress1 = QPushButton("Run")
layout = QVBoxLayout()
layout.addWidget(self.textEdit)
layout.addWidget(self.btnPress1)
self.setLayout(layout)
self.btnPress1.clicked.connect(self.btnPress1_Clicked)
self.aio_conn = aio_connection()
self.aio_conn.data_recvd.connect(self.on_data_ready)
def btnPress1_Clicked(self):
threading.Thread(target=bg_task, args=(self.cb,)).start()
#QtCore.Slot()
def on_data_ready(self, msg):
print('on_data_ready:', threading.get_native_id())
self.textEdit.append(msg)
def cb(self, info):
print('cb:', threading.get_native_id())
self.aio_conn.data_recvd.emit(info)
def bg_task(callback):
print('bg_task:', threading.get_native_id())
for i in range(5):
callback(str(i))
time.sleep(0.1)
def main():
print('main:', threading.get_native_id())
app = QApplication(sys.argv)
win = TextEditDemo()
win.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

Unable to connect to a Signal sent in a QTimer Slot if Slot is called before connecting the signal

Py3 / PySide2 5.13.2 on Windows
I have two classes: one clock emitting a Signal with a timer / one display printing the time
import PySide2
from PySide2 import QtCore, QtWidgets, QtGui
from PySide2.QtWidgets import QApplication, QWidget
from PySide2.QtCore import Signal, Slot
import time
import sys
class MyClock(QtCore.QObject):
"""
Must inherited QObject to emit a Signal
"""
def __init__(self):
super().__init__()
self.timer = QtCore.QTimer()
self.timer.start(1000.)
def Start(self):
self.timer.timeout.connect(self.Tick)
def Tick(self):
t = time.time()
print ("Emit: ",t)
self.emit(QtCore.SIGNAL("SEND(float)"),t)
class ClockDisplay():
def __init__(self):
super(ClockDisplay,self).__init__()
def Display(self,t):
print ("Received: ", t)
BUG: the QTimer slot (Tick) is called (clock.Start) BEFORE connecting the signal to display:
if __name__ == "__main__":
app = QApplication(sys.argv)
clock = MyClock()
clock.Start()
display = ClockDisplay()
clock.connect(QtCore.SIGNAL("SEND(float)"),display.Display)
Warning Message
*** Sort Warning ***
Signals and slots in QMetaObject 'MyClock' are not ordered correctly, this may lead to issues.
1 Slot Tick()
2! Signal SEND(float)
clock.connect(QtCore.SIGNAL("SEND(float)"),display.Display)
and SEND is not received by display.
WORKS : the QTimer slot (Tick) is called (clock.Start) AFTER connecting the signal to display.
if __name__ == "__main__":
app = QApplication(sys.argv)
clock = MyClock()
display = ClockDisplay()
clock.connect(QtCore.SIGNAL("SEND(float)"),display.Display)
clock.Start()
For me in both cases they work only that I get the warning in the first case. This warning only indicates that the signals and slots are expected to be ordered in the QMetaObject but in this case they are not because first the "Tick" slot was added and then the "SEND" signal. Probably Qt or shiboken use the signals and slots in some way that requires some ordering, so it throws that warning.
My recommendation is that you don't use the old syntax of creating signals and slots but use "Signal" and "Slot":
from PySide2 import QtCore
import time
import sys
class MyClock(QtCore.QObject):
SEND = QtCore.Signal(float)
def __init__(self):
super().__init__()
self.timer = QtCore.QTimer()
self.timer.start(1000.0)
def Start(self):
self.timer.timeout.connect(self.Tick)
def Tick(self):
t = time.time()
print("Emit: ", t)
self.SEND.emit(t)
class ClockDisplay:
def Display(self, t):
print("Received: ", t)
if __name__ == "__main__":
app = QtCore.QCoreApplication(sys.argv)
clock = MyClock()
clock.Start()
display = ClockDisplay()
clock.SEND.connect(display.Display)
sys.exit(app.exec_())

How to can I add threading to PyQt5 GUI?

So I have created a GUI using QT Designer. It works pretty well, but on more complex calls it doesn't update the main window and locks up. I want to run my CustomComplexFunction() while updating a textEdit in the main window from constantly changing backend information, and I wanted it to run every 2 seconds. The following code seems right and runs without errors, but doesn't update the textEdit. Please note i'm importing a .ui file designed from QT Designer with a pushButton and textEdit and the code won't run without it.
Main.py
import sys
from PyQt5.QtWidgets import QDialog, QApplication, QPushButton, QVBoxLayout, QMainWindow
from PyQt5.QtCore import QCoreApplication, QObject, QRunnable, QThread, QThreadPool, pyqtSignal, pyqtSlot
from PyQt5 import uic, QtGui
class Worker(QObject):
newParams = pyqtSignal()
#pyqtSlot()
def paramUp(self):
x=1
for x in range(100):
time.sleep(2)
self.newParams.emit()
print ("sent new params")
x +=1
Ui_somewindow, _ = uic.loadUiType("mainwindow.ui") #the path to UI
class SomeWindow(QMainWindow, Ui_somewindow, QDialog):
def __init__(self):
QMainWindow.__init__(self)
Ui_somewindow.__init__(self)
self.setupUi(self)
# Start custom functions
self.params = {}
self.pushButton.clicked.connect(self.complex) #NumEvent
def complex(self):
self.work = Worker()
self.thread = QThread()
self.work.newParams.connect(self.changeTextEdit)
self.work.moveToThread(self.thread)
self.thread.start()
self.CustomComplexFunction()
def CustomComplexFunction(self):
self.params['City'] = 'Test'
def changeTextEdit(self):
try:
City = self.params['City']
self.textEdit_14.setPlainText(City)
except KeyError:
City = None
if __name__ == "__main__":
app = QApplication(sys.argv)
window = SomeWindow()
window.show()
sys.exit(app.exec_())
You can see the official docs for Signals and Slots here and this SO post was also very helpful, but it seems like I built it correctly. According to the docs, the emitter doesn't care if the signal is used. This might be why the code doesn't have errors but doesn't work either.
Any ideas on how to make it work? Or atleast some way to test the emitter and signals??
You have forgot to connect the thread to the worker object.
self.work = Worker()
self.thread = QThread()
self.thread.started.connect(self.worker.work) # <--new line, make sure work starts.
self.thread.start()
Good luck with the application :-)

How to call a method asynchronously in PyQt5 using Python3?

How to call a method asynchronously in PyQt5 using Python3?
I have tried to use a signal to do it.
import sys
from time import sleep
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
class MainWindow(QMainWindow):
asyncFuncSignal = pyqtSignal()
def __init__(self):
super().__init__()
self.initUi()
def initUi(self):
self.label = QLabel(self)
self.label.setText("loading...")
# Trying to call 'self.asyncFunc' asynchronously
self.asyncFuncSignal.connect(self.asyncFunc)
self.asyncFuncSignal.emit()
print("loaded")
def asyncFunc(self):
# Doing something hard that takes time
# I have used 'sleep' to implement the delay
sleep(2)
self.label.setText("done")
print("asyncFunc finished")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
This program tries to finish asyncFunc before writing "loaded". But I would like the program to finish initUi immediately with shown loading... in the label and after that the text done appears in 2 seconds.
What is the best and the shortest way to do it?
It's said here http://pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html I can use queued connections but I haven't found an example how to implement it.
PyQt5.QtCore.Qt.QueuedConnection may help. Just replace
self.asyncFuncSignal.connect(self.asyncFunc)
with
from PyQt5.QtCore import Qt
...
self.asyncFuncSignal.connect(self.asyncFunc, Qt.QueuedConnection)

PyQt5 Signals and Threading

I watched a short tutorial on PyQt4 signals on youtube and am having trouble getting a small sample program running. How do I connect my signal being emitted from a thread to the main window?
import cpuUsageGui
import sys
import sysInfo
from PyQt5 import QtCore
"""Main window setup"""
app = cpuUsageGui.QtWidgets.QApplication(sys.argv)
Form = cpuUsageGui.QtWidgets.QWidget()
ui = cpuUsageGui.Ui_Form()
ui.setupUi(Form)
def updateProgBar(val):
ui.progressBar.setValue(val)
class ThreadClass(QtCore.QThread):
def run(self):
while True:
val = sysInfo.getCpu()
self.emit(QtCore.pyqtSignal('CPUVALUE'), val)
threadclass = ThreadClass()
# This section does not work
connect(threadclass, QtCore.pyqtSignal('CPUVALUE'), updateProgBar)
# This section does not work
if __name__ == "__main__":
threadclass.start()
Form.show()
sys.exit(app.exec_())
The signal must be created, inside your ThreadClass, or before but as you emit the signal inside the ThreadClass, it is better to create it inside your class.
After creation, you need to connect it to the progress bar function. Here is an example of the signal created and connected inside your class.
class ThreadClass(QtCore.QThread):
# Create the signal
sig = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(ThreadClass, self).__init__(parent)
# Connect signal to the desired function
self.sig.connect(updateProgBar)
def run(self):
while True:
val = sysInfo.getCpu()
# Emit the signal
self.sig.emit(val)
Keep in mind that signals have changed style since PyQt5 : Description
if you watched a tutorial for PyQt4, it is not be the same.

Categories