Search code examples
pythonpyqtpyqt5signals-slotsqtreewidgetitem

alternatives to emit a signal from a QTreeWidgetItem SubClass PyQt/PySide


I am implementing a gui analysis software that uses a QTreeWidget with a couple of custom TreeWidgetItem classes. Each item in the tree is responsible more or less for its own book keeping in keeping track of datasets and objects attached to it. I'd like to be able to emit a signal from within the functions of the Custom QTreeWidgetItems, but the following code results in an error that myTWItem cannot be converted to a QObject. Is there any way to emit a signal from a QTreeWidgetItem in this way?

import sys
from PyQt5 import QtCore, QtWidgets, QtGui


class myTWItem(QtWidgets.QTreeWidgetItem):
    childAdded = QtCore.pyqtSignal(object)

    def addChild(self, child):
        super(myTWItem, self).addChild(child)
        self.childAdded.emit(child)

app = QtWidgets.QApplication(sys.argv)
tw = QtWidgets.QTreeWidget()

item = myTWItem()

tw.addTopLevelItem(item)
item.setText(0, "James")

child = QtWidgets.QTreeWidgetItem()
child.setText(0, "Braddock")
item.addChild(child)

tw.show()
sys.exit(app.exec_())

outputs:

>>Traceback (most recent call last):
>>  File "C:/Coding/Python/dataquick/sandbox/treewidgettest.py", line 22, in <module>
>>    item.addChild(child)
>>  File "C:/Coding/Python/dataquick/sandbox/treewidgettest.py", line 10, in addChild
>>    self.childAdded.emit(child)
>>TypeError: myTWItem cannot be converted to PyQt5.QtCore.QObject in this context
>>
>>Process finished with exit code 1

Solution

  • One way to do this would be to define the custom signals on the parent tree-widget. Then, from the item, you would be able to do:

    tree = self.treeWidget()
    if tree is not None:
        tree.childAdded.emit(child)
    

    Of course, the obvious downside here, is that this won't work for items that haven't yet been added to a tree (although, in some scenarios, that might be an advantage).

    Another way would be to create a separate signals class that inherits QObject. To keep things lightweight, it would be best to use a shared instance of this, rather creating one for each item. This would also avoid having to make connections for every item in the tree:

    import sys
    from PyQt5 import QtCore, QtWidgets, QtGui
    
    class TWSignals(QtCore.QObject):
        childAdded = QtCore.pyqtSignal(object)
    
    class myTWItem(QtWidgets.QTreeWidgetItem):
        signals = TWSignals()
    
        def addChild(self, child):
            super(myTWItem, self).addChild(child)
            self.signals.childAdded.emit(child)
    
    app = QtWidgets.QApplication(sys.argv)
    tw = QtWidgets.QTreeWidget()
    
    # connect using the class attribute
    myTWItem.signals.childAdded.connect(
        lambda item: print('child added:', item.text(0)))
    
    item = myTWItem()
    
    tw.addTopLevelItem(item)
    item.setText(0, "James")
    
    child = QtWidgets.QTreeWidgetItem()
    child.setText(0, "Braddock")
    item.addChild(child)
    
    tw.show()
    sys.exit(app.exec_())
    

    (NB: of course, doing things in either of these two ways means that sender() won't return what you'd normally expect it to).