QWidget becomes unresponsive when trying to update progressbar from external script [duplicate] - python

This question already has an answer here:
PyQt QProgressBar not working when I use it with Selenuim
(1 answer)
Closed 3 years ago.
I'm trying to learn how to update a progress bar in my main script when my for loop is running in an external script. It's my first time trying to use signals so I'm unsure if I've used them correctly. Here is what I've tried so far.
The first thing that I tried doing was creating a class "Signal" which would have the pyqtSignal that is keeping track of the progress bar. Then I created an a signal object and I have it emit a signal every time the for loop runs. Then in my main script I try connecting the signal object that I created to the setValue method of the progressbar. The progress bar updates till 5% and after that the window becomes unresponsive. I have two main questions regarding the code:
What is the reason for the window becoming unresponsive?
How to get the progress bar in the main script to update so that the window doesn't become unresponsive?
main.py
import sys
from external import *
from PyQt5.QtWidgets import QWidget, QApplication, QProgressBar,
QPushButton, QVBoxLayout
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('Testing')
self.setGeometry(300, 300, 290, 150)
self.vbox = QVBoxLayout()
self.setLayout(self.vbox)
self.progressbar = QProgressBar(self)
self.button = QPushButton("run", self)
self.vbox.addWidget(self.progressbar)
self.vbox.addWidget(self.button)
c.update.connect(self.progressbar.setValue)
self.button.clicked.connect(test)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
external.py
from PyQt5.QtCore import pyqtSignal, QObject
import time
class Signal(QObject):
update = pyqtSignal(int)
c = Signal()
def test():
for i in range(100):
c.update.emit(i)
time.sleep(1)

Even if the for loop code is in another file it doesn't matter, as it's executed within the PyQt QApplication event loop (the one started with app.exec_()).
A time.sleep blocks everything until completed, including the GUI, that's why the interface becomes unresponsive.
You need to run the loop function in a separate thread.
This is a very basic solution:
import threading
[...]
def initUI(self):
[...]
c.update.connect(self.updateProgressBar)
self.button.clicked.connect(self.runTest)
self.show()
def runTest(self):
threading.Thread(target=test).start()
self.button.setEnabled(False)
def updateProgressBar(self, value):
self.progressbar.setValue(value)
if value == 100:
self.button.setEnabled(True)
NB: I've set the range of the test function to 101, so that when it reaches 100 it enables the button again, since I've disabled it to avoid multiple and concurrent executions of the function.
That said, I wouldn't suggest you to follow this kind of approach, as it would certainly create issues in the future.
Have a look at this answer, which might let you understand a better (and suggested) approach with threads.

Related

Understanding how contextMenuEvent (or any event in general) works in PyQt5

Below is the code that generates an empty window that when you right-click it prints "Context menu event!". I'm just wondering (roughly) how this is implemented by PyQt5 because I don't feel comfortable treating it as a complete black box. So I guess here you overwrite contextMenuEvent of QMainWindow by reducing it to a mere print function, but how does this have anything to do with right-clicking? What are the steps that PyQt5 takes from the moment I right-click to when "Context menu event!" is printed?
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
def contextMenuEvent(self, event):
print("Context menu event!")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
PyQt5 basically waits for the event to happen, and then after it happend routes it to the object the event is targeted for.
With app.exec_() you are starting the Qt event loop. The event loop will wait for events (user input, timers, network traffic, sensors, etc.) in your case that means the mouse click, and after recieving it, it will dispatch the event to the objects that have subscribed to them (in your case your main window). Event loops are a common concept in programming see wikipedia.
You can find the code for the Qt eventloop here: https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qeventloop.cpp.html#224

How to show tooltip while not focusing at pyqt5 python?

I want to show tooltip while not focusing.
I made the code by referring to this PyQt Window Focus
But, it works after click window just one. Works fine, but window always blink at taskbar.
And I think this method is inefficient.
I think it's as if os are not resting while waiting for task to come, but checking every moment for task to come.
This is a simple window window, so it won't use up the cpu much, but I want to code it more efficiently.
Is there any way to improve this?
Or this method right because focusoutEvent excuted only one? ( Cpu resource 0% )
If right, how can I remove blink at taskbar?
I check reference focusPolicy-prop
import sys, os
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class MyApp(QWidget):
def __init__(self):
super().__init__()
self.setFocusPolicy(QtCore.Qt.ClickFocus)
self.initUI()
def initUI(self):
vbox = QVBoxLayout()
vbox.addStretch(2)
btn = QPushButton("Test")
btn.setToolTip("This tooltip")
vbox.addWidget(btn)
vbox.addStretch(1)
self.setLayout(vbox)
self.setGeometry(300, 300, 300, 200)
self.show()
def focusOutEvent(self, event):
self.setFocus(True)
self.activateWindow()
self.raise_()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyApp()
sys.exit(app.exec_())
You are having an XY problem: trying to find a solution (usually unorthodox and overly complicated) for a problem that is originated elsewhere.
What you want to do is to show tooltips even if the window is not focused, not to restore the focus of the window; to achieve this you must not reactivate the window when it loses focus (which not only is WRONG, but is both a wrong way and reason for doing so).
You just have to set the WA_AlwaysShowToolTips widget attribute on the top level window (and remove the unnecessary focusOutEvent override, obviously).
class MyApp(QWidget):
def __init__(self):
super().__init__()
self.setFocusPolicy(QtCore.Qt.ClickFocus)
self.initUI()
self.setAttribute(QtCore.Qt.WA_AlwaysShowToolTips, True)
Note that the attribute must be set on a widget that is a top level window, so, unless you're using a QMainWindow or you are absolutely sure that the QWidget will always be a window, it's usually better to do this instead:
self.window().setAttribute(QtCore.Qt.WA_AlwaysShowToolTips, True)
Besides that, the blinking is normal on windows, and has nothing to do with CPU usage:
activateWindow():
[...] On Windows, if you are calling this when the application is not currently the active one then it will not make it the active window. It will change the color of the taskbar entry to indicate that the window has changed in some way. This is because Microsoft does not allow an application to interrupt what the user is currently doing in another application.

how to send a value from one class to another using pyqt4 and python

i'm having some trouble in building my app, I have a GUI made in pyqt4 and Python, I use QThread to check the cpu load every 2 seconds and I want to diplay it on a progress bar. My GUI is in one class and my Qthread is in the other.
This is my code: pyqt classes, my code printscreen
I want to know how to pass my values collected in QThread to Qobjects in my other Class.
import sys,os,module,config_read,time,threading,datecs_print,mysql.connector as mariadb,commandList
import psutil,logging
from PyQt4 import QtGui, uic ,QtSql,QtCore
from PyQt4.QtCore import QThread, SIGNAL
import resources
import webbrowser
sys.stderr = open("errlog.txt", "w")
class systemValues(QThread):
def __init__(self):
QThread.__init__(self)
def __del__(self):
self.wait()
def cpuRunValue(self):
while (1):
for x in range(2):
p = psutil.cpu_percent(1,False)
return p
def cpuProgressBarUpdate(self):
while(1):
# MyWindow.setcpuBarValue(val=77)
MyWindow.cpuBar.setValue(value=77)
def run(self):
# self.cpuRunValue()
print(self.cpuRunValue())
# self.cpuProgressBarUpdate()
class MyWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QDialog.__init__(self)
super(MyWindow, self).__init__()
file_path = os.path.abspath("ui/sales_window.ui")
uic.loadUi(file_path, self)
self.myThread = systemValues()
self.myThread.start()
def setcpuBarValue(self):
threading.Thread(target=self.cpuBar.setValue(systemValues.cpuRunValue())).start()
This is my code, I get no error. I just cant transfer my value I get from cpuRunValue() to QprogressBar from MyWindow. I'm not very experienced with this.
PS: I eliminated lots of code that's not necessary, but please let me know if you need more info.
Thank You.
If you want to use threads in Qt then you must understand them well. For instance read this page first. Certainly don't mix the QThread with the threading package from the Python standard library.
I hope you're not offended if I think that your probably fairly new to programming. I advise you to not use threads (in general and in Qt) until you have more experience. Threads are hard to use correctly, even for seasoned programmers, and therefore should only be used if necessary. For this reason the Qt documentation on threads even includes a section about alternatives.
You want to execute a function every 2 seconds, so in your case the best solution is to use a QTimer and connect its timeout signal to a slot that updates the progress bar. Like so:
#!/usr/bin/env python
import sys
import psutil
from PyQt5 import QtCore, QtWidgets
class MyWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.layout = QtWidgets.QVBoxLayout()
self.setLayout(self.layout)
self.progressBar = QtWidgets.QProgressBar()
self.layout.addWidget(self.progressBar)
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.updateCpu)
self.timer.start(100) # Every 0.1 seconds
def updateCpu(self):
cpu = psutil.cpu_percent(None, False)
#print("cpu = {}%".format(cpu))
self.progressBar.setValue(cpu)
def main():
app = QtWidgets.QApplication(sys.argv)
win = MyWidget()
win.show()
win.raise_()
app.exec_()
if __name__ == "__main__":
main()
Note that I changed the first parameter of cpu_percent to None. This makes the call return immediately instead of lasting one second (and therefore you can update the progress bar more often if you want). Setting it to None causes the first call of cpu_percent to return a meaningless value, but in your cause that's not an issue I think. See the documention.
Finally, even though you removed a lot of unnecessary code, your code example was not yet minimal (e.g. the import datecs_print is not necessary to run it, and I don't have this module). I was also not complete (e.g. the "ui/sales_window.ui" file was missing so I couldn't run it). Please read the page on how to make an MCVE. You will get a lot more help next time if you include a small program that we can just copy-paste-execute.
Hope this helps

QTimer runs after Dialog has been closed

I have a QDialog, where i create a QTimer Object, which triggers every n seconds a function. After closing the Dialog (hitting the x button), the timer is still firing and seems to be not destroyed. How can i stop it? Currently as a workaround i am explicitly calling Qtimer.stop() when entering the closeEvent()?
I would expect that every class-member is deleted, when the Window is closed, even when i call the Deconstructor explicitly, the Qtimer object persists.
from PyQt4 import QtGui, QtCore
import sys
def update():
print "tick..."
class Main(QtGui.QDialog):
def __init__(self, parent = None):
super(Main, self).__init__(parent)
self.timer = QtCore.QTimer()
self.timer.timeout.connect(update)
self.timer.start(2000)
# scroll area
self.scrollArea = QtGui.QScrollArea()
self.scrollArea.setWidgetResizable(True)
# main layout
self.mainLayout = QtGui.QVBoxLayout()
self.setLayout(self.mainLayout)
def closeEvent(self, evt):
print "class event called"
self.timer.stop()
myWidget = Main()
myWidget.show()
http://doc.qt.io/qt-5/timers.html
The main API for the timer functionality is QTimer. That class
provides regular timers that emit a signal when the timer fires, and
inherits QObject so that it fits well into the ownership structure of
most GUI programs. The normal way of using it is like this:
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(updateCaption()));
timer->start(1000);
The QTimer object is made into a child of this widget so that, when
this widget is deleted, the timer is deleted too. Next, its timeout()
signal is connected to the slot that will do the work, it is started
with a value of 1000 milliseconds, indicating that it will time out
every second.
In C++, the timers are parented to the widget or another QObject, and then their lifespan is tied to the lifespan of the QObject, but it is still good practice to stop a timer when you don't need it. The layouts get parented when you call setLayout. The timer doesn't know its parent so it doesn't get destroyed when the widget gets destroyed. It just sits on the heap, still getting ran by the QApplication event loop.
http://doc.qt.io/qt-5/qobject.html#setParent
So either pass in self to the constructor of the QTimer, or call setParent on the QTimer to set it into the object tree properly.
http://doc.qt.io/qt-5/objecttrees.html
UPDATE: Apparently setParent isn't working in PyQt. Just pass in self in the QTimer constructor.
Hope that helps.

QWidget showFullScreen produces multiple resizeEvents

I have a QT application written in python using PySide and I stumbled across a little problem regarding the showFullScreen method of the QGLWidget (although the problem occurs with every other widget too):
The problem is, that the widget doesn't have its 'final' resolution after the program returns from showFullScreen.
The switch seems to be triggered asynchronously between 5 and 10 milliseconds later.
This is a problem for me because I have to do some layout calculations which depend on the widget's size after it is shown.
Below is a little reproducer which subclasses QGLWidget. Using this reproducer you will take notice, that resizeEvent will be called twice after showFullScreen.
I'm looking for a convinient way of knowing which resizeEvent is the 'final' one, or a way of knowing, when the widget really is in fullscreen mode. Is there maybe any signals I could connect to?
Thanks a lot for any help on this.
#!/usr/bin/python
import sys
from PySide.QtGui import QApplication
from PySide.QtCore import QTimer
from PySide.QtOpenGL import QGLWidget
class TestWidget(QGLWidget):
def __init__(self, parent=None):
super(TestWidget, self).__init__(parent)
self._timer = QTimer()
self._timer.setInterval(5)
self._timer.timeout.connect(self.showsize)
self._timer.start()
def resizeEvent(self, event):
print "Resize event:", event.size().width(), event.size().height()
def showsize(self):
w = widget.size().width()
print "Timer: ", w, widget.size().height()
if w == 1680:
self._timer.stop()
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = TestWidget()
widget.showFullScreen()
print "After showFullScreen:", widget.size().width(), widget.size().height()
# this will always be 640 480...1680 1050 is what I'm expecting
app.exec_()
One possibility is to check that if the resize event is spontaneous or not. From limited testing here (using Qt C++ on Linux), the second resize event is spontaneous, while the first one is not.
You could do your calculations only when the event is spontaneous.
Note: I'm not sure how portable this is, it might depend on your window manager/windowing system.

Categories

Resources