8

What is the 'correct' or idiomatic way to cleanup/delete widgets when using PyQt4?

Consider the following code:

choices = ['a', 'b', 'c']
checkboxes = []
layout = QtGui.QVBoxLayout()

dialog = MyDialog()

for c in choices:
    checkboxes.append(QtGui.QCheckBox(c)
    layout.addWidget(chkbox)

dialog.setLayout(layout)

for c in checkboxes:
    c.setParent(None)
    c.deleteLater()
    c = None

The above code uses setParent(), deleteLater(), and setting the object to None. Are all of these necessary?

Another possible scenario is I have a dialog with a bunch of widgets on it and want to remove these widgets and add new ones. I don't want to 'leak' the old widgets, but I'm not sure what the correct way to do something like this would be.

It seems to me that deleteLater() might never be needed. Does it just decrement the reference count? If so, wouldn't just setting the variable to None do the same thing?

durden2.0
  • 9,222
  • 9
  • 44
  • 57

1 Answers1

18

First thing you should remember to do is to use the parent/child relationships for your widgets. When you do this, they will be owned by Qt and will automatically clean up all children when the parent is deleted.

dialog = MyDialog()

for c in choices:
    checkboxes.append(QtGui.QCheckBox(c, parent=dialog))
    layout.addWidget(chkbox)

In this situation, all the checkboxes will be properly cleaned up when you delete dialog. This handles one part of your question. I realize that you are implicitly having the parent set when you add them to the layout. But you should not then clear that parent before deleting. It is the parent relationship that allows the child to be automatically removed. Not a reference count. The reference aspect would be a python-side thing where it will get garbage collected when there are no more references to it.

deleteLater is very important, for use when you want the deletion to occur when control returns to the eventloop. It is also the safe way to delete widgets when you are removing some from a layout and adding new ones:

# clear a layout and delete all widgets
# aLayout is some QLayout for instance
while aLayout.count():
    item = aLayout.takeAt(0)
    item.widget().deleteLater()

These widgets will actually be deleted once this method has completed. deleteLater is also useful for deleting the widget under which the slot or event is currently occurring. Such as a QPushButton that can delete itself on click.

There is also not much need to set c = None. Once a parent is deleted, and that triggers the deletion of all of its children, recursively, your python references to that object will be invalid. So all you need to do is to just not use them anymore. If they are in a list, clear the list. Accessing them would raise the RuntimeError: wrapped C/C++ object of %S has been deleted meaning they are deleted.

jdi
  • 90,542
  • 19
  • 167
  • 203
  • Is it necessary to use the `parent` argument when creating all objects that will be added to a layout? I realize this is probably a bit more explicit, but is it required? – durden2.0 Dec 12 '12 at 14:49
  • No not really. If you are adding them directly to the layout then the layout will make them children of the owner of the layout. – jdi Dec 12 '12 at 15:09
  • Very late to the party, but thought I'd add that — although it's not _necessary_ to use the `parent` argument — it's probably a good idea, since it constrains the structure of your code a little, and makes it easier for others to see what the intended containment hierarchy is. – Emmet Jul 30 '13 at 23:40
  • @jdi you say "accesing them would raise the `RuntimeError: wrapped C/C++ object` error" - how can I check if an object is invalid / has been deleted? I need a way to check if `c` has been deleted but I don't know how. See my question: http://stackoverflow.com/questions/27420338/how-to-clear-child-window-reference-stored-in-parent-application-when-child-wind – memyself Dec 11 '14 at 12:38
  • 2
    @memyself - The only way to check if it has been deleted without trying to access a method and catching the RuntimeError, is to use either the sip module for PyQt or the shiboken module for PySide. They would allow you to query of the pointer was deleted. Other than that, you can just hit the objectName() method to catch the error. – jdi Dec 11 '14 at 18:04