QTimer runs after Dialog has been closed - python

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.

Related

different ways of calling class method with button clicks

I am exploring how to call class methods in PyQt applications. As a test, I created a widget that initializes a worker, puts it on a separate thread, and starts the thread. I also created two buttons:
The left button connects to the run function of the worker directly
The right button connects to a widget method that calls the worker run function
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
from time import sleep
class Worker(QObject):
worker_finished = pyqtSignal()
def __init__(self):
super().__init__()
#pyqtSlot()
def run(self):
print("Sleeping")
for ii in range(5):
print(ii)
sleep(1)
print("Finished sleeping")
class MainApp(QWidget):
def __init__(self):
super().__init__()
self._threads = []
#Create the worker
self.worker = Worker()
layout = QHBoxLayout(self)
#Move the worker on the thread and start
self.put_on_thread_and_start(self.worker)
#This button calls worker.run() directly
button = QPushButton("Call \"\"run\"\" method directly")
button.clicked.connect(self.worker.run)
layout.addWidget(button)
#This button calls a widget method, which calls worker.run()
button = QPushButton("Call \"\"run\"\" method through this widget class method")
button.clicked.connect(self.make_the_worker_run)
layout.addWidget(button)
def put_on_thread_and_start(self, worker_class):
myThread = QThread()
self._threads.append(myThread)
worker_class.moveToThread(myThread)
print("Starting thread...")
myThread.start()
def make_the_worker_run(self):
self.worker.run()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainApp()
window.show()
sys.exit(app.exec_())
When I click the left button, the worker executes in the background, as expected. However, when I click the right button the widget freezes until the worker has finished running. What's the difference between the two approaches?
When a signal is emitted to a function (slot) and that signal is emitted, Qt detects if the signal emission and the receiving objects are in the same thread. If they are not, the slot is executed in the receiver's thread and control is immediately returned in the sender instead. This is what allows to use threads on Qt without blocking the "main Qt thread" (which is responsible of keeping the UI responsive).
A very important thing to understand is that Qt is able to detect what thread emitted the signal and on what thread the slot is, then it decides if the slot can be directly executed or not.
In your first case, the button (which is going to emit the signal) and the self.worker.run are on different threads and Qt knows that when it tries to call run; the result is that the function will be executed in the other thread.
In the second case, Qt only knows about make_the_worker_run, which from its perspective is in the same thread of the button: Qt doesn't know anything about what you actually do in that function. The fact that run is in a method of an object that has been moved to another thread doesn't mean anything, and the result is that the function will be executed in the main thread, hence the blocking.
Read more about this topic on the Qt docs about Threads and QObjects.
There is an incorrect conception of how threads, QThread, signals, etc. work.
When a QObject is moved to a QThread it only tells the Qt eventloop that if it invokes a slot of that QObject it must be executed in the thread that manages the QThread (if the Qt::QueuedConnection or Qt::AutoConnection flag is used in the connection). And how does the Qt eventloop invoke the functions? Well, for this use the signals, timers, QMetaObject::invokedMethod, QEvents, etc.
The above explains why the first method works: By using the clicked signal to invoke "run" it notifies the eventloop that it should invoke the method, and using the previous rule it will invoke the thread that manages the QThread.
In the second method instead you are invoking it directly in the main thread which blocks the eventloop of the main thread freezing the GUI.

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

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

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.

Closing a QWidget opened from a QMainWindow

I need to show a QWidget, which code is written in another module, when a certain button is pressed. To accomplish this, I wrote this code:
class Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
#A lot of stuff in here
#The button is connected to the method called Serial_connection
self.connect(self.btn_selection_tool3, SIGNAL("clicked()"), self.Serial_connection)
def Serial_connection(self):
LiveData.LiveData(self).show()
Doing this, I open a QWidget and it works fine. But, when I want to close this QWidget, I can not do it. This is the code of the QWidget:
class LiveData(QWidget):
def __init__(self,parent = None):
super(QWidget, self).__init__(parent)
#Another stuff in here
#I create a "close" button connected to another method
self.connect(self.closeBtn, QtCore.SIGNAL("clicked()"), self.StopAndClose)
def StopAndClose(self):
print "Closing window"
self.close() #HERE IS WHERE I HAVE THE PROBLEM
I´ve tried several options like: self.close(), self.accept() or even sys.exit(1). The problem with the latter sys.exit(1) is that it closes the QWidget and the QMainWindow. So, how can I close this QWidget only? Hope you can help me.
You probably want your QWidget to be a QDialog. If it's a temporary modal widget, you should be calling the dialog like this
dialog = LiveData.LiveData(self)
dialog.exec_()
If you just want to show the dialog at the same time as your main window, and users are meant to interact with both (though from a design perspective, this doesn't sound like a great idea), you can continue to use .show()
Also, you should use the new-style signal/slot syntax. The old syntax hasn't been used for many years.
self.closeButton.clicked.connect(self.StopAndClose)
Though, for a QDialog you can just do
self.closeButton.clicked.connect(self.accept)

How to move an object back and forth between QThreads in Pyqt

In my program (using Python 2.7), I create an object containing some important data and methods. Some of the methods are CPU hungry, so in certain cases I move the object to a new QThread for the duration of the CPU intensive methods, then have them come back to the main thread. At a later point, when a CPU intensive method is called, I would like to move the object to another QThread again, however this fails saying "Current thread is not the object's thread".
Here is a trivial example which reproduces the problem:
import sys
from PyQt4 import QtCore, QtGui
from time import sleep
class ClassA(QtGui.QDialog):
def __init__(self):
super(ClassA, self).__init__()
mainLayout=QtGui.QVBoxLayout()
self.lineEdit=QtGui.QLineEdit()
mainLayout.addWidget(self.lineEdit)
self.setLayout(mainLayout)
self.show()
self.obj=ClassC(self)
self.executeProgram()
def executeProgram(self):
self.lineEdit.setText("Starting new thread...")
self.thread=QtCore.QThread()
self.obj.moveToThread(self.thread)
self.thread.started.connect(self.obj.doWork)
self.obj.doingWork.connect(self.updateGui)
self.obj.finished.connect(self.killThread)
self.thread.start()
def updateGui(self,message):
self.lineEdit.setText(message)
def killThread(self):
self.thread.quit()
self.thread.wait()
self.obj.finished.disconnect()
self.executeProgram()
class ClassC(QtCore.QObject):
finished=QtCore.pyqtSignal()
doingWork=QtCore.pyqtSignal(str)
def __init__(self,parent=None):
super(ClassC, self).__init__()
def doWork(self):
for i in range(5):
self.doingWork.emit("doing work: iteration "+str(i))
sleep(1)
self.finished.emit()
if __name__=="__main__":
app=QtGui.QApplication(sys.argv)
obj=ClassA()
app.exec_()
Is it possible to move an object to a different QThread multiple times? If so, how would I fix my code to do this?
Note moveToThread must be called on the thread that the object currently belongs to, so you may need to move the object back to the main thread before moving it to yet another thread.
Add the line mainThread = QtCore.QThread.currentThread() somewhere at the top, and put self.moveToThread(mainThread) right before emitting the finished signal.

Categories

Resources