How to update and refresh in PyQt5 at a higher frequency - python

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
class MainWindow(QWidget):
signal = pyqtSignal()
def __init__(self):
super(MainWindow, self).__init__()
self.buttonList = list()
self.counter = 0
self.setUI()
self.signal.connect(self.updatebutton)
self.startTimer(10)
def timerEvent(self, *args, **kwargs):
self.signal.emit()
def updatebutton(self):
for button in self.buttonList: # type: QLabel or QPushButton
button.setText(str(self.counter))
self.counter += 1
def setUI(self):
gridLayout = QGridLayout()
for i in range(10):
for j in range(10):
button = QLabel(self)#QPushButton(self)
self.buttonList.append(button)
gridLayout.addWidget(button, i, j)
self.setLayout(gridLayout)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
When I set the timer at 10 ms, all Labels can increase its value by 100 per second. However, when I change the timer to 1 ms, all Labels can not increase its value by 1000 per second.
When I use Pushbutton instead of Label, even the timer is set to 100 ms, all buttons can not increase their value by 100 per second. But when I reduce the number of buttons, e.g., totally 10 buttons instead of 100, these buttons can work well.
3.I meet with this problem recently when I used a data acquisition software called DEWESOFT, which can produce many subwidget or subwindow(very similar to QMidArea and QMidSubwindow) to draw curves and display values of high-frequency signals. Hence, I'd like to find out how and why. I wonder how to control the refresh rate of widget and how to determine the limit of UI refresh ability. If I wanna refresh more widgets at a high frequency, what should I do?
Later I tried Thread to improve, it seems to work a litter better, but I don't know why.
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import threading
import sys
import time
class MainWindow(QWidget):
signal = pyqtSignal()
def __init__(self):
super(MainWindow, self).__init__()
self.buttonList = list()
self.counter = 0
self.setUI()
self.signal.connect(self.updatebutton)
self.thread = threading.Thread(target=self.timerevent, args=())
self.thread.setDaemon(True)
self.thread.start()
# self.startTimer(10)
def timerEvent(self, *args, **kwargs):
self.signal.emit()
def timerevent(self):
while True:
self.signal.emit()
time.sleep(0.01)
def updatebutton(self):
print(self.counter)
for button in self.buttonList: # type: QPushButton
button.setText(str(self.counter))
self.counter += 1
def setUI(self):
gridLayout = QGridLayout()
for i in range(10):
for j in range(10):
button = QPushButton(self)
self.buttonList.append(button)
gridLayout.addWidget(button, i, j)
self.setLayout(gridLayout)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

Related

Python PyQt5 load Image for QPushButton on QThread

So I have this UI that loads ALOT of buttons, all with Images, problem is, it takes WAY to long, so I tried putting it into a QThread, I had it working, but there was no speed difference, so I tried a different solution, but now the Thread wont start.
Code:
import sys
import os
from PyQt5 import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui, QtPrintSupport, QtWidgets, uic
# I've tried using a QThread for this, but it still took the same amount of time.
class LoadImageThumbnailshread(QThread):
def __init__(self, listButtons, listPaths):
QThread.__init__(self)
self.listButtons = listButtons
self.listPaths = listPaths
def run(self):
self.process_images_Thread()
def process_images_Thread(self):
for i, j in enumerate(self.listButtons):
j.setIcon(QIcon(self.listPaths[i]))
j.setIconSize(QSize(150-6, 60-6))
class App(QDialog):
def __init__(self):
super().__init__()
self.title = 'PyQt5 layout - pythonspot.com'
self.left = 10
self.top = 10
self.width = 320
self.height = 100
self.images_path = []
self.button_images = []
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.createGridLayout()
windowLayout = QVBoxLayout()
windowLayout.addWidget(self.horizontalGroupBox)
self.setLayout(windowLayout)
self.show()
def createGridLayout(self):
self.horizontalGroupBox = QGroupBox("Grid")
layout = QGridLayout()
layout.setColumnStretch(1, 4)
layout.setColumnStretch(2, 4)
for i in range(100):
self.btnImage = QPushButton()
self.btnImage.setObjectName('btnImage')
self.images_path.append(os.path.dirname(
os.path.abspath(__file__)) + 'view.png')
# ! I have also tried using Pixmaps with Clickable labels, but it made no diffrence.
# pixmap = QPixmap(os.path.dirname(os.path.abspath(__file__)) + image_locations[i])
# pixmap = pixmap.scaled(60, 300, Qt.KeepAspectRatio, Qt.FastTransformation)
# self.btnImage.setPixmap(pixmap)
# ! Enableing this loads the images, but very slowly
# self.btnImage.setIcon(QIcon(os.path.dirname(os.path.abspath(__file__)) + '/view.png'))
# self.btnImage.setIconSize(QSize(150-6, 60-6))
self.button_images.append(self.btnImage)
layout.addWidget(self.btnImage, i, 0)
# ! This starts the QThread
converter = LoadImageThumbnailshread(
self.button_images, self.images_path)
converter.start()
self.horizontalGroupBox.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
So to explain more of what I want, I want to start a QThread AFTER the UI has loaded, that QThread will load all of the Images onto each of the buttons. Problem is: it doesn't work right now.
Another problem that I had, is that when I got the QThread the UI waited for it to finish, and then everything all of a sudden popped up. I want the QThread to be completely separate and pretty, much see all images loading one by one if you will.
The threads do not serve to accelerate any task!!! but to execute tasks that do not block any thread. Given the above, if I have n "task" and each one is executed in "n" threads then the total execution time will be 1 task, that is, no task is accelerated but the tasks are redistributed. In conclusion: If you want to speed up a task then threads is not a default option as it depends on the application.
On the other hand, the loading of the image does not consume time but there are many tasks that consume little time that in total is equivalent to a task that consumes a lot of time and unfortunately that task cannot be executed in another thread or process since the elements of the GUI are not thread-safe.
A workaround is that the load is of a small group of n elements every T seconds so the total task will be distributed and the user will not observe any effect of lag.
import os
import sys
from PyQt5.QtCore import QSize, QTimer
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import (
QApplication,
QDialog,
QGridLayout,
QGroupBox,
QPushButton,
QVBoxLayout,
)
class App(QDialog):
def __init__(self):
super().__init__()
self.title = "PyQt5 layout - pythonspot.com"
self.left = 10
self.top = 10
self.width = 320
self.height = 100
self.images_path = []
self.button_images = []
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.createGridLayout()
windowLayout = QVBoxLayout(self)
windowLayout.addWidget(self.horizontalGroupBox)
self._iter = iter(range(100))
self._timer = QTimer(interval=10, timeout=self.lazy_loading)
self._timer.start()
def createGridLayout(self):
self.horizontalGroupBox = QGroupBox("Grid")
self.grid_layout = QGridLayout()
self.grid_layout.setColumnStretch(1, 4)
self.grid_layout.setColumnStretch(2, 4)
self.horizontalGroupBox.setLayout(self.grid_layout)
def lazy_loading(self):
try:
i = next(self._iter)
except StopIteration:
self._timer.stop()
else:
btn = QPushButton()
image_path = os.path.join(os.path.abspath(__file__), "view.png")
btn.setIcon(QIcon(image_path))
btn.setIconSize(QSize(150 - 6, 60 - 6))
self.grid_layout.addWidget(btn, i, 0)
if __name__ == "__main__":
app = QApplication(sys.argv)
ex = App()
ex.show()
sys.exit(app.exec_())

How to get the current Value of ProgressBar in pyQt5?

I'm Learning Pyqt5.
The problem is that i want to get the value of the progressbar in pyqt5
i tried using self.progressBar.getValue() or self.progressBar.getInt() None of them worked
The actual code is a little big.but never mind. Please help
i just need the syntax for getting the current Value from the progressbar ie: between 1 to 100
According to their documentation, the method for getting the value is just value(), so in your case, it would be self.progressBar.value()
I agree with #dustin-we, here is a minimal code example:
import sys
import time
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar, QPushButton)
TIME_LIMIT = 100
class Actions(QDialog):
"""
Simple dialog that consists of a Progress Bar and a Button.
Clicking on the button results in the start of a timer and
updates the progress bar.
"""
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('Progress Bar')
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 25)
self.progress.setMaximum(100)
self.button = QPushButton('Start', self)
self.button.move(0, 30)
self.show()
self.button.clicked.connect(self.onButtonClick)
def onButtonClick(self):
count = 0
while count < TIME_LIMIT:
count += 1
time.sleep(0.01)
self.progress.setValue(count)
# !! Here is the command you need !!
print(self.progress.value())
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
sys.exit(app.exec_())

PyQt4 QMessageBox text change

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_()

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)

Python3, PyQt5: QProgressBar updating makes actual task very slow

I am using Python 3.5, PyQt5 on OSX and I was wondering if there was a possibility to update the QProgressBar without slowing down the whole computing work.
Here was my code and if I did just the task without the progressbar update it was soo much faster!!
from PyQt5.QtWidgets import (QWidget, QProgressBar, QPushButton, QApplication)
from jellyfish import levenshtein_distance, jaro_winkler
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.pbar = QProgressBar(self)
self.pbar.setGeometry(30, 40, 200, 25)
self.btn = QPushButton('Start', self)
self.btn.move(40, 80)
self.btn.clicked.connect(self.doAction)
self.setGeometry(300, 300, 280, 170)
self.show()
def doAction(self):
#setup variables
step = 0
m = 1000
n = 500
step_val = 100 / (m * n)
#make task
for i in range(m):
for j in range(n):
jaro_winkler(str(i), str(j))
#show task
print(i,j)
#update progressbar
step += step_val
self.pbar.setValue(step)
QApplication.processEvents()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
Then with help from stackoverflow users I got the hint to make a separate working thread and connect the updating signal to the GUI. I did it and it looks now like the following code. It also works and is much faster, but I can't figure out how to connect the emited signal to the GUI. Can somebody please help me? Many thanks in advance!
from jellyfish import jaro_winkler
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QProgressBar, QMainWindow
import time
import numpy as np
class Main_Window(QMainWindow):
def __init__(self):
super(Main_Window,self).__init__()
self.initUI()
def initUI(self):
self.pbar = QProgressBar(self)
self.pbar.setGeometry(30, 40, 200, 25)
self.btn = QPushButton('Start', self)
self.btn.move(40, 80)
self.btn.clicked.connect(MyThread.doAction)
self.setGeometry(300, 300, 280, 170)
self.show()
def updateProgressBar(self, val):
self.pbar.setValue.connect(val)
class MySignal(QWidget):
pbar_signal = QtCore.pyqtSignal(int)
class MyThread(QtCore.QThread):
def __init__(self):
super().__init__()
def doAction(self):
t = time.time() #for time measurement
#setup variables
step = 0
m = 1000
n = 500
pbar_val = 100 / m
signal_instance = MySignal()
#make task
for i in range(m):
for j in range(n):
jaro_winkler(str(i), str(j))
signal_instance.pbar_signal.emit(pbar_val)
#measuring task time
print(np.round_(time.time() - t, 3), 'sec elapsed')
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = Main_Window()
sys.exit(app.exec_())
There are three things slowing the code down:
Printing to stdout is very expensive - especially when you do it 500,000 times! On my system, commenting out print(i,j) roughly halves the time doAction takes to run.
It's also quite expensive to call processEvents 500,000 times. Commenting out QApplication.processEvents() reduces the run time by another two-thirds.
Commenting out self.pbar.setValue(step) halves the time again.
Hopefully it should be obvious by now that attempting to update the gui 500,000 times during a task that should take less than a second is massive overkill! Most user's reaction times are about 200ms at best, so you only need to update the gui about once every 100ms.
Given that, one simple fix is to move the updates into the outer loop:
for i in range(m):
for j in range(n):
jaro_winkler(str(i), str(j))
# show task
# print(i,j)
step += step_val
# update progressbar
self.pbar.setValue(step)
QApplication.processEvents()
But an even better solution would be to move the calculations into a separate worker thread and have it periodically emit a custom signal to update the progress bar:
class Main_Window(QMainWindow):
...
def initUI(self):
...
self.btn.clicked.connect(self.doAction)
self.thread = MyThread()
self.thread.pbar_signal.connect(self.pbar.setValue)
def doAction(self):
if not self.thread.isRunning():
self.thread.start()
class MyThread(QtCore.QThread):
pbar_signal = QtCore.pyqtSignal(int)
def run(self):
#for time measurement
t = time.time()
#setup variables
m = 1000
n = 500
progress = step = 100 / m
#make task
for i in range(m):
for j in range(n):
jaro_winkler(str(i), str(j))
progress += step
self.pbar_signal.emit(progress)
#measuring task time
print(np.round_(time.time() - t, 3), 'sec elapsed')

Categories