PyQt5: Update labels inrun time - python

I have a problem updating labels in a loop during run time. I think I need to use signals and so on, but I've tried everything I can think of now. What I want my program to do:
When I click the button a loop should start that run some function that take some time. While the function is running the corresponding label should update its text to say "running" and when its done it should say "done" and continue to the next function. I've created some dummy code that represent what I'm after!
I have represented my function with a dummy function that just take some time.
import sys
from PyQt5.QtWidgets import *
import time
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# Set up an example ui
qbtn = QPushButton('Click', self)
qbtn.clicked.connect(self.changeLabels)
qbtn.resize(qbtn.sizeHint())
qbtn.move(100, 50)
# Add labels
self.labels = list()
self.labels.append(QLabel("Lbl1", self))
self.labels[-1].setFixedSize(50, 20)
self.labels.append(QLabel("Lbl2", self))
self.labels[-1].setFixedSize(50, 20)
self.labels[-1].move(0, 20)
self.labels.append(QLabel("Lbl3", self))
self.labels[-1].setFixedSize(50, 20)
self.labels[-1].move(0, 40)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Test')
self.show()
def changeLabels(self):
# Loop over all labels. For each label a function will be executed that will take some time. In this case
# I represent that with a dummy function to just take time. While the function is running the label should say
# "running" and when its finished it should say "done".
for lbl in self.labels:
orgTxt = lbl.text()
lbl.setText("%s Running" % orgTxt)
self.dummyFunction()
lbl.setText("%s Done" % orgTxt)
def dummyFunction(self):
time.sleep(1)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

The GUI does not support blocking tasks because the GUI needs some time to update some attributes of the window, a possible solution is to use a new thread and implement the dummy function, in the following example the implementation is shown with the use of signals. If your real function updates any GUI view you should not do it directly in the thread, you should do it through signals.
class DummyThread(QThread):
finished = pyqtSignal()
def run(self):
time.sleep(1)
self.finished.emit()
class Example(QWidget):
[...]
def changeLabels(self):
for lbl in self.labels:
orgTxt = lbl.text()
lbl.setText("%s Running" % orgTxt)
thread = DummyThread(self)
thread.start()
thread.finished.connect(lambda txt=orgTxt, lbl=lbl : lbl.setText("%s Done" % txt))

An easier way would be to call QApplication.processEvents() to force QT to process all the events present on the queue. But the UI won't be as responsive as it would be with another thread doing the work and sending signals to the main thread as stated in other answers.

Related

Using a QTimer within a PyQt worker thread

I am working with serial device and set a flag (which is global variable) based on the received data. Now I want to reset the flag after a while (for example one second) by using a timer.
Here is the code:
class Inlet_Worker(QObject):
def __init__(self):
super(Inlet_Worker, self).__init__()
self.timer = QTimer(self)
self.timer.timeout.connect(self.Reset_Register_Barcode)
def run(self):
global Register_Barcode
while True :
if client.read_coils(address = 0x0802).bits[0]:
Register_Barcode = True
self.timer.start(1000)
def Reset_Register_Barcode(self):
global Register_Barcode
Register_Barcode = False
However the timer is not working.
I will assume from your example code that your are using a QThread and that you also use QObject.moveToThread on your worker object. This is the correct procedure, but there are some other things you must do to make your timer work.
Firstly, you should use a single-shot timer so as to avoid re-regsitration whilst the current one is active. Secondly, you must explicitly process any pending events, since your while-loop will block the thread's event-loop. Without this, the timer's timeout signal will never be emitted. Thirdly, you should ensure that the worker and thread shut down cleanly when the program exits (which will also prevent any Qt error messages). Finally, if possible, you should use signals to communicate registration changes to the main GUI thread, rather than global variables.
The demo script below (based on your example) implements all of that. After the Start button is clicked, the thread will start and periodically update the regsitration (indicated by the check-box). Hopefully you shoudld be able to see how to adapt it to your real application:
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class Inlet_Worker(QObject):
barcodeRegistered = pyqtSignal(bool)
def __init__(self):
super().__init__()
self._stopped = False
self._registered = False
self.timer = QTimer(self)
self.timer.setSingleShot(True)
self.timer.timeout.connect(self.updateBarcodeRegistration)
def run(self):
count = 0
self._stopped = False
while not self._stopped:
#if client.read_coils(address = 0x0802).bits[0]:
count += 1
if count % 20 == 0 and not self._registered:
self.updateBarcodeRegistration(True)
self.timer.start(2000)
QCoreApplication.processEvents()
QThread.msleep(100)
self.updateBarcodeRegistration(False)
self.timer.stop()
print('Stopped')
def updateBarcodeRegistration(self, enable=False):
print('Register' if enable else 'Reset')
self._registered = enable
self.barcodeRegistered.emit(enable)
def stop(self):
self._stopped = True
class Window(QWidget):
def __init__(self):
super().__init__()
self.thread = QThread()
self.worker = Inlet_Worker()
self.worker.moveToThread(self.thread)
self.button = QPushButton('Start')
self.check = QCheckBox('Registered')
layout = QHBoxLayout(self)
layout.addWidget(self.button)
layout.addWidget(self.check)
self.thread.started.connect(self.worker.run)
self.button.clicked.connect(self.thread.start)
self.worker.barcodeRegistered.connect(self.check.setChecked)
def closeEvent(self, event):
self.worker.stop()
self.thread.quit()
self.thread.wait()
if __name__ == '__main__':
app = QApplication(['Test'])
window = Window()
window.setGeometry(600, 100, 200, 50)
window.show()
app.exec()

Break an infinit loop when button is pressed

I am currently starting to use PyQt5 and created my first GUI. Now I would like a program that does the following. When Button 'start' is pressed execute 'function' n-times until Button 'stop' is pressed (imagine a stopwatch) if I press Button 'start' again 'function' gets once again executed and so on.
What I tried so far is the following, that can not work, since we are not able to change a variable from outside the loop (mockup-script)
class Ui(QtWidgets.QDialog):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi('interfaceScan.ui', self)
self.startScan = self.findChild(QtWidgets.QPushButton, 'startScan')
self.startScan.clicked.connect(self.startPressed)
self.pauseScan = self.findChild(QtWidgets.QPushButton, 'pauseScan')
self.pauseScan.clicked.connect(self.pausePressed)
self.show()
def startPressed(self):
global pauseScan
pauseScan = False
dosomething()
def pausePressed(self):
global pauseScan
pausScan = True
def dosomething():
while pauseScan == False: #not needed, but the measurement should be executed periodically until 'pause' is pressed
print('Running') #in the reals program the measurement will be executed here
time.sleep(4) #between each measurement the program needs to wait a cirtain amount of time ~1h
app = QtWidgets.QApplication(sys.argv)
window = Ui()
app.exec_()
Any ideas on how I can solve this problem? I am now relatively certain that it does not work when using a while loop, so I am open to suggestions on how to change it up!
The purpose of this script will be to control a measurement setup that should run for let's say 1000 cycles, but I would like to be able to break it in between to change so parameters.
As the OP points out in the comments, the while loop with the time.sleep() only aims to perform a periodic task but this generates a problem since the time.sleep() freezes the GUI, instead a QTimer must be used:
from PyQt5 import QtCore, QtWidgets, uic
class Ui(QtWidgets.QDialog):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi("interfaceScan.ui", self)
self.startScan.clicked.connect(self.startPressed)
self.pauseScan.clicked.connect(self.pausePressed)
self.timer = QtCore.QTimer(self, interval=4 * 1000, timeout=dosomething)
#QtCore.pyqtSlot()
def startPressed(self):
QtCore.QTimer.singleShot(0, dosomething)
self.timer.start()
#QtCore.pyqtSlot()
def pausePressed(self):
self.timer.stop()
def dosomething():
print("Running")
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = Ui()
window.show()
sys.exit(app.exec_())

PyQt: Progress bar and label disappear

I am new to Python and PyQt. When I start my program, after a few seconds, the progress bar and label disappear. The progress bar starts appearing and disappearing (the label is gone) when the mouse hovers over the progress bar, showing up once more before disappearing. But if I comment the line where I set up the progress bar value, the label does not disappear.
Here is the code:
from PyQt4 import QtCore, QtGui, Qt
from PyQt4.Qt import QDialog, QApplication
import sys
import sensors
from sensors import *
import threading
class tmp():
def main(self):
global test
global name
sensors.init()
try:
for chip in sensors.iter_detected_chips():
#print (chip)
#print('Adapter:', chip.adapter_name)
for feature in chip:
if feature.label == 'Physical id 0':
test = feature.get_value()
name = feature.label
#print ('%s (%r): %.1f' % (feature.name, feature.label, feature.get_value()))
threading.Timer(5.0, self.main).start()
return test
print
finally:
sensors.cleanup()
zz = tmp()
zz.main()
class MainWindow(QtGui.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.setGeometry(50, 50, 250, 150)
self.setWindowTitle("Title here")
#lay = QtGui.QVBoxLayout()
#lay.addWidget(self.prgB)
#lay.addWidget(self.lbl)
#self.setLayout(lay)
self.home()
def home(self):
self.prgB = QtGui.QProgressBar(self)
self.prgB.setGeometry(20, 20, 210, 20)
self.lbl = QtGui.QLabel(self)
self.lbl.setGeometry(20, 40, 210, 20)
lay = QtGui.QVBoxLayout()
lay.addWidget(self.prgB)
lay.addWidget(self.lbl)
self.setLayout(lay)
self.update()
def update(self):
textas = ('%s : %.1f' % (name, test))
self.lbl.setText(str(textas))
self.prgB.setValue(test)
threading.Timer(5.0, self.update).start()
QtGui.QApplication.processEvents()
self.show()
def run():
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
app = QtGui.QApplication(sys.argv)
GUI = MainWindow()
sys.exit(app.exec_())
run()
What I am trying to do is just get the temp value (pysensors) and pass it to the label text and progress bar value. It is working, just as I said, but after a few seconds the label is gone and the progress bar disappears.
I know (or I guess) there is something wrong with the update function. I just can't find out whats wrong.
First of all you don't need the separate class tmp(). Delete it and just move the main() function in MainWindow class. After doing this name,test variables should not be global any more. Define them in your init (for example self.test = 0, self.name='something') and refer to them in the rest of the code as self.test and self.name.
Now the most important mistake in your code is that you are trying to update GUI components from a different thread. GUI components should be handled only by the main thread using the signal/slot mechanism that pyqt provides.
The steps for doing this in your case are
Define a new signal in MainWindow class
Connect this signal to the update() function
Emit this signal from main() function
In the end your code should look like this
class MainWindow(QtGui.QWidget):
signalUpdateBar = QtCore.pyqtSignal()
def __init__(self):
...
self.test = 0
self.name = "0"
...
def home(self):
...
self.signalUpdateBar.connect(self.update)
self.main()
self.show()
def main():
try:
...
self.test = feature.get_value()
self.name = feature.label
threading.Timer(5.0, self.main).start()
self.signalUpdateBar.emit()
finally:
...
Moreover in your update() function
self.prgB.setValue(self.test)
should be the last statement. Anything below that is not necessary.

Python: How to Link Multi-Processes to Multi-Progress Bars

It seems I've got dangerously close to being able to link all the sub-processes initiated by subprocessing's Pool() to PyQt Progress Bar widgets. Here is the example code. Apparently there are few flows that stop my dream from happening.
The concept:
externalFunc() performs all the main tasks. From time to time it sends its 'progress values' to MainWindow() instance by storing its VALUE into poolDict variable declared with:
poolDict=manager.dict()
Meanwhile myEvenListener() method is running at the background awaiting for VALUE to be 'posted'. As soon as VALUE "received" myEvenListener() updates a ProgressBar to a received VALUE and resets it back to zero (to avoid the duplicate progressBar updates).
myEvenListener(self) before trying to update ProgressBar verifies the ProgressBar didn't yet reach its 100 maximum value before proceeding by using:
if pb.value()>=100: continue
Unfortunately even while externalFunc() supplies with more than enough values (160) the ProgressBar never reaches 100. Aside from it there is no way to exit MainWindow() while loop.
Please be carefull running this code before you read it since it may generate multi-python-processes on your machine that will have to be terminated.
Question:
Why does ProgressBar never reach its maximum 100
How to make sure myEvenListener() method is not running when it is not needed.
How to make sure all the sub-processes die after:
a. all the progressBars are at their 100%
b. user closes dialog box or terminates main process.
import sys, time
from PyQt4 import QtCore, QtGui
import multiprocessing as mp
from multiprocessing import Pool
manager = mp.Manager()
poolDict=manager.dict()
class PbWidget(QtGui.QProgressBar):
def __init__(self, parent=None, total=20):
super(PbWidget, self).__init__()
self.setMinimum(1)
self.setMaximum(total)
self._active = False
def update_bar(self, to_add_number):
while True:
time.sleep(0.01)
value = self.value() + to_add_number
self.setValue(value)
QtGui.qApp.processEvents()
if (not self._active or value >= self.maximum()):
break
self._active = False
def closeEvent(self, event):
self._active = False
def externalFunc(each):
for i in range(16):
print i
poolDict[each]=10+i
time.sleep(0.5)
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
# self.myList=[.1, .3, .5, 1.0, 1.5, 2.9, 3.1]
self.myList=[1]
self.main_layout = QtGui.QVBoxLayout()
self.pBars={}
self.state=True
for each in self.myList:
pb=PbWidget(total=101)
self.main_layout.addWidget(pb)
self.pBars[each]={'pb':pb, 'value':0, 'total_value':0}
ok_button = QtGui.QPushButton("OK")
ok_button.clicked.connect(self.OK)
self.main_layout.addWidget(ok_button)
central_widget = QtGui.QWidget()
central_widget.setLayout(self.main_layout)
self.setCentralWidget(central_widget)
def myEvenListener(self):
"""This function runs at the background as an infinite while loop. It constantly reads
a value stored in poolDict variable to which externalFunc() writes a new value every second.
The value represents a 'Progress' and needs to be passes to Progress Bar widget to which only MainWindow()
class has an access. After a Value was read it is used to update a Progress Bar.
After a progress bar was updated the Value is reset to zero.
The Problem: 'if pb.value()>=100' statement is used to make sure the function doesn't loop if the ProgressBar
is already reached 100. By some reason the bar never reaches its maximum 100 even while externalFunc() calls
enough times to reach this number. An ussue # 2: There is no meachanism to exit this while loop without adding
three more variables... Can it be achieved with what we already have?
"""
while self.state:
for each in self.pBars:
pb = self.pBars[each]['pb']
print "\n Current Bar value =", pb.value()
if pb.value()>=100:
print 'skipping'
continue
value=None
if each in poolDict.keys():
# read delivered value
delivered_value=poolDict[each]
# reset delivered value
poolDict[each]=0
# update bar with delivered value
if ( 101-pb.value() ) < delivered_value:
print "\n\t UPDATING WITH "
pb.update_bar( 101-pb.value() )
print "\n\t AFTER ", pb.value()
else:
pb.update_bar( delivered_value )
# print '\n\t\t\t Updating bar using this value', delivered_value
def OK(self):
pool = Pool(processes=3)
pool.map_async( externalFunc, self.myList)
self.myEvenListener()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.resize(480, 320)
window.show()
sys.exit(app.exec_())
Here is a revised code. It seems to be working well and it is quite stable.
import sys, time
from PyQt4 import QtCore, QtGui
import multiprocessing as mp
from multiprocessing import Pool
manager = mp.Manager()
poolDict=manager.dict()
class PbWidget(QtGui.QProgressBar):
def __init__(self, parent=None, total=20):
super(PbWidget, self).__init__()
self.setMinimum(1)
self.setMaximum(total)
self._active = False
def update_bar(self, to_add_number):
while True:
time.sleep(0.01)
value = self.value() + to_add_number
self.setValue(value)
QtGui.qApp.processEvents()
if (not self._active or value >= self.maximum()):
break
self._active = False
def closeEvent(self, event):
self._active = False
def externalFunc(each):
for i in range(16):
value =10+i
poolDict[each]=value
time.sleep(each)
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.myList=[.5, .8, 1.2, 1.8, 2.2, .3, .1]
self.main_layout = QtGui.QVBoxLayout()
self.pBars={}
self.state=True
self.name=None
for each in self.myList:
pb=PbWidget(total=101)
self.main_layout.addWidget(pb)
self.pBars[each]={'pb':pb, 'name':each}
ok_button = QtGui.QPushButton("Distribute")
ok_button.clicked.connect(self.OK)
self.main_layout.addWidget(ok_button)
central_widget = QtGui.QWidget()
central_widget.setLayout(self.main_layout)
self.setCentralWidget(central_widget)
def myEvenListener(self):
"""This function runs at the background as an infinite loop. It is constantly reading
a value stored in poolDict variable to which externalFunc() writes a new value.
The value represents a 'Progress' and needs to be passed to Progress Bar widget to which MainWindow()
class has an access. After a Value was read and used to update a Progress Bar it is reset to zero.
"""
entities = self.pBars.keys()
while self.state:
for each in entities:
if each not in self.pBars.keys(): continue
pb = self.pBars[each]['pb']
if pb.value()>=100:
self.pBars.pop(each, None)
value=None
if each in poolDict.keys():
# read delivered value
delivered_value=poolDict[each]
# reset delivered value
poolDict[each]=0
# update bar with delivered value
if ( 101-pb.value() ) < delivered_value:
pb.update_bar( 101-pb.value() )
elif delivered_value>0:
pb.update_bar( delivered_value )
if len(self.pBars.keys())==0:
self.state=False
def OK(self):
pool = Pool(processes=3)
pool.map_async( externalFunc, self.myList)
self.myEvenListener()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.resize(480, 320)
window.show()
sys.exit(app.exec_())

Problems with the order execution functions when use PyQT4

I have a problem with PyQT4 for Python. There is a label with text and button connected to function. The function could change the text of label first, then call other function. There is a problem with it: the function is executed firts, then change text of the label.
Code:
# -*- coding: utf-8 -*-
import time
import sys
from PyQt4 import QtCore, QtGui
def timesleep():
print("start sleep")
time.sleep(5)
print("stop sleep")
class AnyWidget(QtGui.QWidget):
def __init__(self,*args):
QtGui.QWidget.__init__(self,*args)
self.setWindowTitle("PETHARD")
boxlay = QtGui.QHBoxLayout(self)
frame = QtGui.QFrame(self) # Фрейм
frame.setFrameShape(QtGui.QFrame.StyledPanel)
frame.setFrameShadow(QtGui.QFrame.Raised)
gridlay = QtGui.QGridLayout(frame) # Менеджер размещения элементов во фрейме
label = QtGui.QLabel(u"Welcome",frame) # Текстовая метка.
global glabel
glabel = label
gridlay.addWidget(label,0,0)
button1 = QtGui.QPushButton(u"Load From MC", frame)
self.connect(button1, QtCore.SIGNAL("clicked()"), self.ts)
gridlay.addWidget(button1,1,0)
boxlay.addWidget(frame)
def ts(self):
global glabel
glabel.setText(u"Waiting...")
timesleep()
if __name__=="__main__":
app = QtGui.QApplication(sys.argv)
aw = AnyWidget()
aw.show()
sys.exit(app.exec_())
Help me please to fix this problem.
It work's like that because rendering is done later in app. So your glabel.text is changed immediately but you will see changed text on screen after your ts function call, because drawing is done at the end of loop.
If you really want to call your function in new frame (after rendering of new text) then use timer:
timer = QtCore.QTimer()
QtCore.QObject.connect(
timer,
QtCore.SIGNAL("timeout()"),
self.ts
)
timer.start(10)
It should call your function ten milisecond later, so in fact probably after rendering.
You never want to tie-up your GUI with a long-running function. That the label does not update is only one manifestation of the problem. Even if you get the label to update before the function is called, it will still "freeze" your GUI -- no widget will respond to the user until the long-running function completes.
If you have to run such a function, see if there is a way to break it up into small pieces which each relinquish control to the GUI, or consider using a separate thread to run the function:
import time
import sys
from PyQt4 import QtCore, QtGui
class TimeSleep(QtCore.QThread):
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
self.parent = parent
def run(self):
print("start sleep")
time.sleep(5)
print("stop sleep")
self.parent.glabel.setText(u"Done")
class AnyWidget(QtGui.QWidget):
def __init__(self, *args):
QtGui.QWidget.__init__(self, *args)
self.setWindowTitle("PETHARD")
boxlay = QtGui.QHBoxLayout(self)
frame = QtGui.QFrame(self) # Фрейм
frame.setFrameShape(QtGui.QFrame.StyledPanel)
frame.setFrameShadow(QtGui.QFrame.Raised)
gridlay = QtGui.QGridLayout(
frame) # Менеджер размещения элементов во фрейме
label = QtGui.QLabel(u"Welcome", frame) # Текстовая метка.
self.glabel = label
gridlay.addWidget(label, 0, 0)
button1 = QtGui.QPushButton(u"Load From MC", frame)
self.connect(button1, QtCore.SIGNAL("clicked()"), self.ts)
gridlay.addWidget(button1, 1, 0)
boxlay.addWidget(frame)
def ts(self):
self.glabel.setText(u"Waiting...")
self.thread = TimeSleep(parent=self)
self.thread.start()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
aw = AnyWidget()
aw.show()
sys.exit(app.exec_())
Here is a wiki page on how to deal with long-running functions.
The text of the label is being changed, but because you're blocking the main thread you're not giving Qt a chance to paint it.
Use QCoreApplication::processEvents and QCoreApplication::flush:
def ts(self):
glabel.setText(u"Waiting...")
QtCore.QCoreApplication.processEvents()
QtCore.QCoreApplication.flush()
timesleep()

Categories