PyQt: Progress bar and label disappear - python

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.

Related

How to use Threading with variables inside a function? PyQt5

I have a big function which freezes my PyQt5 program, I tried to use a different thread for it (I use QThread for that). Problem is my function needs some variables to work properly.
How to make this works? I show what I did.
Original code:
class AnalysisWindow(QtWidgets.QMainWindow):
def __init__(self, firstWindow):
super(AnalysisWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.pushButton.clicked.connect(self.letsgo)
def letsgo(self):
#here some code , not big
#and at some point i have a heavy one which make it freeze until it's over:
self.x1, self.x2, self.y1,self.y2, self.z, = self.analyze(self.i1, self.i2, self.i3)
def analyze(self,i1,i2,i3):
#big function
return(x1,x2,y1,y2,z)
what I tried :
from PyQt5.QtCore import Qt, QThread, pyqtSignal
class AnalysisWindow(QtWidgets.QMainWindow):
class MyThread(QThread):
_signal =pyqtSignal()
def __init__(self):
super().__init__()
def run(self,i1,i2,i3): # here I obviously can't put variables
#I copied here my analyze function
return(x1,x2,y1,y2,z)
self._signal.emit()
def __init__(self, firstWindow):
super(AnalysisWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.pushButton.clicked.connect(self.letsgo)
def letsgo(self):
self.thread = MyThread()
self.thread.start()
#here I dont see how to send the variables self.i1, self.i2, self.i3 and how to get the result: x1,x2,y1,y2,z
I created the thread class inside the QMainWindow class because i need to pass some variables (self.i1, self.i2, self.i3) from QMainWindow to the function which will use the new thread. Maybe that's bad, but it doesn't work in any way. Thanks everyone.
Here is a minimal working example that you can adapt it to your code.
Few things to note:
You should not inherit from QThread. Instead, you should create a worker and move it into your thread.
In worker, instead of trying to return a result, emit the signal that holds the result and process that signal in your application.
Similarly, instead of trying to call your worker normally, communicate it through its slots via QtCore.QMetaObject.invokeMethod. Once your thread is started, you can call this method as much as you want.
Refer to this answer for more
import sys
import random
from PyQt5.QtCore import QThread, pyqtSignal, QObject, pyqtSlot, Qt
from PyQt5 import QtWidgets
from PyQt5 import QtCore
class Analyzer(QObject):
analyze_completed = pyqtSignal(bool)
analyze_result = pyqtSignal(list, int)
#pyqtSlot(str, list)
def analyze(self, foo, analyze_args):
print(foo, analyze_args)
self.analyze_completed.emit(False)
# do your heavy calculations
for i in range(10000000):
x = i ** 0.5
result = sum(analyze_args)
self.analyze_result.emit(analyze_args, result)
self.analyze_completed.emit(True)
class AnalysisWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.label = QtWidgets.QLabel("")
self.i = 0
self.label_i = QtWidgets.QLabel("Value of i: {}".format(self.i))
self.increment_button = QtWidgets.QPushButton("increment i")
self.pushbutton = QtWidgets.QPushButton("Analyze")
super(AnalysisWindow, self).__init__()
self.analyze_args = []
self.analyzer = Analyzer()
self.thread = QThread()
self.analyzer.analyze_result.connect(self.on_analyze_result_ready)
self.analyzer.analyze_completed.connect(self.on_analyze_completed)
self.analyzer.moveToThread(self.thread)
self.thread.start()
self.init_UI()
def init_UI(self):
grid = QtWidgets.QGridLayout()
grid.addWidget(self.label, 0, 0)
grid.addWidget(self.pushbutton)
grid.addWidget(self.label_i)
grid.addWidget(self.increment_button)
self.increment_button.clicked.connect(self.increment_i)
self.pushbutton.clicked.connect(self.start_analyze)
self.setLayout(grid)
self.move(300, 150)
self.setMinimumSize(300, 100)
self.setWindowTitle('Thread Test')
self.show()
def start_analyze(self):
self.analyze_args.clear()
self.analyze_args.extend(random.choices(range(100), k=5))
QtCore.QMetaObject.invokeMethod(self.analyzer, 'analyze', Qt.QueuedConnection,
QtCore.Q_ARG(str, "Hello World!"),
QtCore.Q_ARG(list, self.analyze_args))
def increment_i(self):
self.i += 1
self.label_i.setText("Value of i: {}".format(self.i))
def on_analyze_result_ready(self, args, result):
t = "+".join(str(i) for i in args)
self.label.setText(f"{t} = {result}")
def on_analyze_completed(self, completed):
if completed:
self.label.setStyleSheet('color: blue')
else:
self.label.setText(
"Analyzing... {}".format(", ".join(str(i) for i in self.analyze_args)))
self.label.setStyleSheet('color: yellow')
app = QtWidgets.QApplication(sys.argv)
widget = AnalysisWindow()
sys.exit(app.exec_())
Hope this helps!

PyQt4: use a QTimer to continually update progress bars

I have a simple dialog with three progress bars that I want to continually update (displaying system resource usage). From reading around the docs, QTimer is the right way to fire a function every x milliseconds (which would update the progress bars). However, I am not able to get it to work and I don't quite know why. It seems relatively simple to connect up the timer timeout signal to an update function, but it never seems to fire.
Here's my code:
import sys
from PyQt4 import QtGui, QtCore
import psutil
class Tiny_System_Monitor(QtGui.QWidget):
def __init__(self):
super(Tiny_System_Monitor, self).__init__()
self.initUI()
def initUI(self):
mainLayout = QtGui.QHBoxLayout()
self.cpu_progressBar = QtGui.QProgressBar()
self.cpu_progressBar.setTextVisible(False)
self.cpu_progressBar.setOrientation(QtCore.Qt.Vertical)
mainLayout.addWidget(self.cpu_progressBar)
self.vm_progressBar = QtGui.QProgressBar()
self.vm_progressBar.setOrientation(QtCore.Qt.Vertical)
mainLayout.addWidget(self.vm_progressBar)
self.swap_progressBar = QtGui.QProgressBar()
self.swap_progressBar.setOrientation(QtCore.Qt.Vertical)
mainLayout.addWidget(self.swap_progressBar)
self.setLayout(mainLayout)
timer = QtCore.QTimer()
timer.timeout.connect(self.updateMeters)
timer.start(1000)
def updateMeters(self):
cpuPercent = psutil.cpu_percent()
vmPercent = getattr(psutil.virtual_memory(), "percent")
swapPercent = getattr(psutil.swap_memory(), "percent")
self.cpu_progressBar.setValue(cpuPercent)
self.vm_progressBar.setValue(vmPercent)
self.swap_progressBar.setValue(swapPercent)
print "updated meters"
def main():
app = QtGui.QApplication(sys.argv)
ex = Tiny_System_Monitor()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You must keep a reference to the timer object, otherwise it will be immediately garbage-collected when initUI returns:
class Tiny_System_Monitor(QtGui.QWidget):
...
def initUI(self):
...
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.updateMeters)
self.timer.start(1000)

PyQt: Window is closed but process still running

Just a simple problem (not for me): when I close the window, the program is still running. Here is the code:
from PyQt4 import QtCore, QtGui
from PyQt4.Qt import QString
import sys
import sensors
from sensors import *
import threading
class MainWindow(QtGui.QWidget):
signalUpdate = QtCore.pyqtSignal() # 1 - define a new signal in mainwindow class
# 2 -connect this signal to the update() function
#emit signal
#main window
def __init__(self):
#vars for core temp and name
self.tempValue = 0
self.name = '0'
super(MainWindow, self).__init__()
self.setGeometry(50, 50, 250, 150)
self.setWindowTitle("Title here")
self.setFixedSize(250, 150)
self.home()
#make widgets (progressBar and labe)
def home(self):
self.prgB = QtGui.QProgressBar(self)
self.prgB.setGeometry(20, 20, 210, 20)
#self.prgB.setOrientation(QtCore.Qt.Vertical)
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create("motif"))#stles -> motif, Plastique, Windows
self.lbl = QtGui.QLabel(self)
self.lbl.setGeometry(60, 40, 210, 20)
self.signalUpdate.connect(self.update) #connect this signal to the update() function
lay = QtGui.QVBoxLayout()
lay.addWidget(self.prgB)
lay.addWidget(self.lbl)
self.setLayout(lay)
self.tmp()
self.show()
#update() to update label and progressbar values
def update(self):
textas = ('%s : %.1f' % (self.name, self.tempValue))
self.lbl.setText(str(textas + ' C'))
self.prgB.setFormat(QString.number(self.tempValue)+ ' C')
self.prgB.setValue(self.tempValue)
#temp() to get chip data from sensors (temp, name etc)
def tmp(self):
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':
self.tempValue = feature.get_value()
self.name = feature.label
#print ('%s (%r): %.1f' % (feature.name, feature.label, feature.get_value()))
threading.Timer(2.0, self.tmp).start()
self.signalUpdate.emit() #emit signal
#print
finally:
sensors.cleanup()
def run():
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
app = QtGui.QApplication(sys.argv)
GUI = MainWindow()
sys.exit(app.exec_())
run()
Why is that happening, and how to fix it? (I was trying to research on google and yes there are many forums with same question but I get no luck so far to fix it).
EDIT: problem is still not fixed, can someone show/tell how to stop threading.Time on program exit? Please :)
Call the timer's cancel() method in your widget's (overridden) closeEvent() method:
def tmp(self):
...
self.timer = threading.Timer(2.0, self.tmp)
self.timer.start()
self.signalUpdate.emit() #emit signal
def closeEvent(self):
self.timer.cancel()
I've tested that this works:
without the threading, app exits;
with the threading.Timer, but without the timer cancel, app never exits;
with the timer cancel, app exits

Multithreading with PySide : is my structure viable?

I'm running into a very strange error while using QThread in PySide. As I run my code, when I click on the button that is supposed to launch the thread and send Signal, I get :
AttributeError: 'builtin_function_or_method' object has no attribute 'displayText'
at line :
self.displayMessage.connect(self.window.displayText)
NB : displayText is a method that I defined in my MainWindow(QWidget) class, while displayMessage is the signal to be emitted.
This error seems, according to the Internet, to occur in various situations, and I couldn't find one that suits my case yet. Therefore I have 2 questions :
Have you guys ever met this error before in a similar situation, and could you (if yes) give me some tips ?
Am I doing it right ? I can't post my code directly down here, so I created a short verifiable example in which I used the same process. Unfortunately, it seems to work perfectly. Please tell me if at least my construction is correct.
Thank you very much.
EDIT : I eventually figured out that I have forgotten a self somewhere in my big code, but the actual error was hidden to me behind this one that I didn't know. I'm still interested in whether or not this code is reliable, and am fully open to suggestions of improvement.
Without thread
When you click "GO !" and try to move the window, you can see that it freezes for a second.
#!/usr/bin/env python
# -*- encoding : utf-8 -*-
import sys
import time
from PySide.QtCore import *
from PySide.QtGui import *
class MainWindow(QWidget):
def __init__(self, qt_app):
QWidget.__init__(self)
worker = Worker(self)
self.qt_app = qt_app
self.setGeometry(100, 100, 220, 40)
self.infoPanel = QTextEdit(self)
self.infoPanel.setReadOnly(True)
self.goButton = QPushButton('GO !', self)
self.goButton.clicked.connect(lambda: worker.displayOnWindow())
self.layout = QVBoxLayout()
self.layout.addWidget(self.infoPanel)
self.layout.addWidget(self.goButton)
self.setLayout(self.layout)
def launch(self):
self.show()
self.qt_app.exec_()
class Worker():
def __init__(self, window):
self.counter = 0
self.window = window
def displayOnWindow(self):
time.sleep(1)
self.window.infoPanel.append(str(self.counter))
self.counter += 1
if __name__=='__main__':
qt_app = QApplication(sys.argv)
mw = MainWindow(qt_app)
mw.launch()
With thread
Now you can move the window without trouble, since the sleeping thread is not the one that displays the window. I've written a sub-class for my thread because in the original code there are some functions that will be called by several buttons.
#!/usr/bin/env python
# -*- encoding : utf-8 -*-
import sys
import time
from PySide.QtCore import *
from PySide.QtGui import *
class MainWindow(QWidget):
def __init__(self, qt_app):
QWidget.__init__(self)
worker = SubWorker(self)
self.qt_app = qt_app
self.setGeometry(100, 100, 220, 40)
self.infoPanel = QTextEdit(self)
self.infoPanel.setReadOnly(True)
self.goButton = QPushButton('GO !', self)
self.goButton.clicked.connect(lambda: worker.start())
self.layout = QVBoxLayout()
self.layout.addWidget(self.infoPanel)
self.layout.addWidget(self.goButton)
self.setLayout(self.layout)
def launch(self):
self.show()
self.qt_app.exec_()
#Slot(int)
def displayOnWindow(self, i):
self.infoPanel.append(str(i))
class Worker(QThread):
displayMessage = Signal(int)
def __init__(self, window):
QThread.__init__(self)
self.counter = 0
self.window = window
self.displayMessage.connect(self.window.displayOnWindow)
def run(self, *args, **kwargs):
return QThread.run(self, *args, **kwargs)
class SubWorker(Worker):
def __init__(self, window):
Worker.__init__(self, window)
def run(self):
time.sleep(1)
self.displayMessage.emit(self.counter)
self.counter += 1
if __name__=='__main__':
qt_app = QApplication(sys.argv)
mw = MainWindow(qt_app)
mw.launch()

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