10

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.

Scintillo
  • 1,634
  • 1
  • 15
  • 29

2 Answers2

10

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)
    
Community
  • 1
  • 1
TobiMarg
  • 3,667
  • 1
  • 20
  • 25
  • Can you possibly share the code that prints Hello World with me? I tried to edit the code according to your instructions but it still doesn't work. http://pastebin.com/ZCgteHu4 – Scintillo May 30 '13 at 19:18
  • That is very strange. The only difference in my code is, that `doSomeDestruction` is defined between the two classes. But now I've found out that it works also only sometimes. Also a [`gc.collect`](http://docs.python.org/3.3/library/gc.html#gc.collect), but it had no effect. I hope we/someone can find a way to make it work. – TobiMarg May 30 '13 at 19:36
  • See my updated answer. I should work now. (no matter where `doSomeDestruction` is defined) – TobiMarg May 30 '13 at 20:04
  • 1
    Actually it seems self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) is all you need to add but thanks :) – Scintillo May 30 '13 at 20:41
4

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']
Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
  • Why not just: `self.destroyed.connect(lambda: print(self.__dict__))`? Or use a closure, as in the OPs original example. (The only reason that example didn't work was because the widgets were never explicitly deleted). – ekhumoro Feb 10 '16 at 02:34
  • @ekhumoro You're normally doing much more substantial operations than just a single print. `lambda`'s only support a single statement. Normally, you'd be interfacing with a handful of other libraries and objects that need to be cleaned up when this object is destroyed. Interesting, I didn't even notice that the original example was using a closure and not a class instance method. I think a closure would work, too. – Brendan Abel Feb 10 '16 at 02:50
  • I suppose I should have asked: what's the specific advantage of using a `staticmethod`? Why not just use *any* ordinary function (doesn't have to be a closure), as opposed to an instance method? – ekhumoro Feb 10 '16 at 03:28
  • @ekhumoro Usually, most signal handlers are defined as instance methods, but in this case, that won't work, because the instance the method is attached to will be deleted by the time it gets called. Most of the business logic related to the handlers is going to be on the class, so it makes sense to make it a staticmethod or classmethod instead of just some random function. – Brendan Abel Feb 10 '16 at 05:25
  • 1
    Sure it can work: just use a `lambda`, as I originally suggested. That is, you can just do: `self.destroyed.connect(lambda: self.method())`. The closure will keep `self` alive, as well as all its attributes and methods (although you must be careful which ones you access whilst the object is being deleted). Of course, if `destroyed` is **directly** connected to an instance method, it cannot work, because the connection will be automatically broken if the method is deleted. – ekhumoro Feb 10 '16 at 19:25