Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Widget's "destroyed" signal is not fired (PyQT)

I have a widget which would have to do some manual cleanup after it's destroyed (stop some threads). However for some reason the "destroyed" signal of the widget is not firing. I have made this small example that demonstrates the problem.

import sys
from PyQt4 import QtGui

class MyWidget(QtGui.QWidget):
    def __init__(self, parent):
        super(MyWidget, self).__init__(parent)

        def doSomeDestruction():
            print('Hello World!')

        self.destroyed.connect(doSomeDestruction)


class MyWindow(QtGui.QMainWindow):
    def __init__(self):
        super(MyWindow, self).__init__()

        self.widget = MyWidget(self)

app = QtGui.QApplication(sys.argv)
window = MyWindow()
window.show()
ret = app.exec_()
sys.exit(ret)

I expected it to print "Hello World!" when the main window is closed. However, it doesn't print anything.

like image 706
Scintillo Avatar asked May 30 '13 17:05

Scintillo


2 Answers

The python class instance (or at least the pyqt<->qt link) doesn't exist by the time destroyed is emitted. You can work around this by making the destroyed handler a staticmethod on the class. This way, the python method will still exist when the destroyed signal is emitted.

class MyWidget(QWidget):

    def __init__(self, parent):
        super(MyWidget, self).__init__(parent)
        self.destroyed.connect(MyWidget._on_destroyed)

    @staticmethod
    def _on_destroyed():
        # Do stuff here
        pass

If you need information specific to the class instance you can use functools.partial and the instance __dict__ to pass that info to the destruction method.

from functools import partial

class MyWidget(QWidget):

    def __init__(self, parent, arg1, arg2):
        super(MyWidget, self).__init__(parent)
        self.arg1 = arg1
        self.arg2 = arg2
        self.destroyed.connect(partial(MyWidget._on_destroyed, self.__dict__))

    @staticmethod
    def _on_destroyed(d):
        print d['arg1']
like image 142
Brendan Abel Avatar answered Sep 22 '22 03:09

Brendan Abel


After a few tries I found out that it works if you declare the doSomeDestruction outside the class. (see at the bottom)
But I don't know why. As written in this answer, this is because At the point destroyed() is emitted, the widget isn't a QWidget anymore, just a QObject (as destroyed() is emitted from ~QObject).
This means when your function would be called it is already deleted if you write it in the class. (look also here in the qt-interest mailing list: Ok , I am sorry for stupid question. The signal is emitted, but the slot is not called for the obvious reason, that is because object is already deleted. )

EDIT: I've found two ways make it really work:

  1. add a simple del window after ret = app.exec_().
  2. Set the WA_DeleteOnClose attribute in the main window (not the widget):
    At the top of the program:

    from PyQt4 import QtCore
    

    The changed __init__ function:

    class MyWindow(QtGui.QMainWindow):
        def __init__(self):
            super(MyWindow, self).__init__()
            self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
            self.widget = MyWidget(self)
    
like image 38
TobiMarg Avatar answered Sep 23 '22 03:09

TobiMarg