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)
Related
I am using this simple code to show warning boxes:
w = QWidget()
result = QMessageBox.warning(w, 'a', x, QMessageBox.Ok)
Is there any way to change MESSAGE dynamically? I want to make a popup which will inform user abut progress of a task that is running in background.
Edit:
Well I tried to do so making this script for testing:
def handleButton(self):
self.msgBox = QMessageBox(self)
self.msgBox.setWindowTitle("Title")
self.msgBox.setIcon(QMessageBox.Warning)
self.msgBox.setText("Start")
self.msgBox.show()
x = 0
for x in range (100):
x = x + 1
print (x)
self.msgBox.setText(str(x))
self.msgBox.show()
time.sleep(1)
The text only shows after finishing the 'for loop', why?
Instead of using a static method you could create an object of the class.
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Widget(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.msgBox = QMessageBox(self)
self.msgBox.setWindowTitle("Title")
self.msgBox.setIcon(QMessageBox.Warning)
self.msgBox.setText("Start")
self.msgBox.show()
timer = QTimer(self)
timer.timeout.connect(self.onTimeout)
timer.start(1000)
def onTimeout(self):
self.msgBox.setText("datetime: {}".format(QDateTime.currentDateTime().toString()))
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
Update:
The problem in your example is the use of time.sleep(). Qt is executed in an eventloop, this eventloop allows you to handle the events of the mouse, keyboard, redraw, etc. but the time.sleep() blocks the eventloop, this you can check trying to change the size of the window, you will see that you can not do it.
Assuming you use time.sleep() to pause, then you must use QEventLoop with QTimer that does not block the Qt eventloop.
def handleButton(self):
self.msgBox = QMessageBox(self)
self.msgBox.setWindowTitle("Title")
self.msgBox.setIcon(QMessageBox.Warning)
self.msgBox.setText("Start")
self.msgBox.show()
for x in range(100):
self.msgBox.setText(str(x+1))
loop = QEventLoop()
QTimer.singleShot(1000, loop.quit)
loop.exec_()
My main objective is to show a constantly evolving value on a Qt-window textEdit. (this window contains only a checkBox and a textEdit).
Sadly, I cannot click on the checkbox and the window is frozen until I shutdown the terminal.
import sys
from threading import Thread
from random import randint
import time
from PyQt4 import QtGui,uic
class MyThread(Thread):
def __init__(self):
Thread.__init__(self)
#function to continually change the targeted value
def run(self):
for i in range(1, 20):
self.a = randint (1, 10)
secondsToSleep = 1
time.sleep(secondsToSleep)
class MyWindow(QtGui.QMainWindow,Thread):
def __init__(self):
Thread.__init__(self)
super(MyWindow,self).__init__()
uic.loadUi('mywindow.ui',self)
self.checkBox.stateChanged.connect(self.checkeven)
self.show()
#i show the value only if the checkbox is checked
def checkeven(self):
while self.checkBox.isChecked():
self.textEdit.setText(str(myThreadOb1.a))
# Run following code when the program starts
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
# Declare objects of MyThread class
myThreadOb1 = MyThread()
myThreadOb2 = MyWindow()
# Start running the threads!
myThreadOb1.start()
myThreadOb2.start()
sys.exit(app.exec_())
At the moment I'm using a thread to set a random value to a, but at the end it is supposed to be a bit more complex as I will have to take the values from an automation.
Do you have any clue why my code is acting like this?
Thank you very much for your help.
The problem is that the while self.checkBox.isChecked() is blocking, preventing the GUI from handling other events.
Also you should not run a PyQt GUI on another thread than the main one.
If you want to send data from one thread to another, a good option is to use the signals.
Doing all these considerations we have the following:
import sys
from threading import Thread
from random import randint
import time
from PyQt4 import QtGui, uic, QtCore
class MyThread(Thread, QtCore.QObject):
aChanged = QtCore.pyqtSignal(int)
def __init__(self):
Thread.__init__(self)
QtCore.QObject.__init__(self)
#function to continually change the targeted value
def run(self):
for i in range(1, 20):
self.aChanged.emit(randint(1, 10))
secondsToSleep = 1
time.sleep(secondsToSleep)
class MyWindow(QtGui.QMainWindow):
def __init__(self):
super(MyWindow,self).__init__()
uic.loadUi('mywindow.ui',self)
self.thread = MyThread()
self.thread.aChanged.connect(self.on_a_changed, QtCore.Qt.QueuedConnection)
self.thread.start()
self.show()
#i show the value only if the checkbox is checked
#QtCore.pyqtSlot(int)
def on_a_changed(self, a):
if self.checkBox.isChecked():
self.textEdit.setText(str(a))
# Run following code when the program starts
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
# Declare objects of MyThread class
w = MyWindow()
sys.exit(app.exec_())
An option more to the style of Qt would be to use QThread since it is a class that handles a thread and is a QObject so it handles the signals easily.
import sys
from random import randint
from PyQt4 import QtGui, uic, QtCore
class MyThread(QtCore.QThread):
aChanged = QtCore.pyqtSignal(int)
def run(self):
for i in range(1, 20):
self.aChanged.emit(randint(1, 10))
secondsToSleep = 1
QtCore.QThread.sleep(secondsToSleep)
class MyWindow(QtGui.QMainWindow):
def __init__(self):
super(MyWindow,self).__init__()
uic.loadUi('mywindow.ui',self)
self.thread = MyThread()
self.thread.aChanged.connect(self.on_a_changed, QtCore.Qt.QueuedConnection)
self.thread.start()
self.show()
#i show the value only if the checkbox is checked
#QtCore.pyqtSlot(int)
def on_a_changed(self, a):
if self.checkBox.isChecked():
self.textEdit.setText(str(a))
# Run following code when the program starts
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
# Declare objects of MyThread class
w = MyWindow()
sys.exit(app.exec_())
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
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.
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()