Search code examples
pythonpyside2

Unable to emit signal from QtGui.QStandardItem


I'm attempting to override QTreeView to handle adjusting parents and children if the checkbox is modified. I'm not able to emit a signal however, and I'm not sure if it's because I'm trying to subclass QtGui and not QtWidgets.

Here is the code that will trigger the error:

class QStandardItem(QtGui.QStandardItem):
    someSignal = QtCore.Signal()
    def __init__(self, *args, **kwargs):
        QtGui.QStandardItem.__init__(self, *args, **kwargs)
        self.someSignal.emit()

>>> QStandardItem()
# AttributeError: 'PySide2.QtCore.Signal' object has no attribute 'emit' # 

Here's my current code just for reference:

class QStandardItem(QtGui.QStandardItem):
    checkStateChanged = QtCore.Signal(object)

    def __init__(self, *args, **kwargs):
        QtGui.QStandardItem.__init__(self, *args, **kwargs)

    def setData(self, data, role):
        if role == QtCore.Qt.CheckStateRole:
            self.checkStateChanged.emit(self)
        QtGui.QStandardItem.setData(self, data, role)


class QTreeView(QtWidgets.QTreeView):
    def __init__(self, *args, **kwargs):
        QtWidgets.QTreeView.__init__(self, *args, **kwargs)

    #I need to know when to trigger this as it edits other nodes
    def checkStateChanged(self, model_index):
        selected_item = self.model().itemFromIndex(model_index)
        check_state = selected_item.checkState()

        #Handle child nodes
        for i in range(selected_item.rowCount()):
            child_item = selected_item.child(i)
            if child_item.isCheckable():
                child_item.setCheckState(check_state)

        #Handle parent nodes
        parent_item = selected_item.parent()
        check_states = {QtCore.Qt.Checked: 0,
                        QtCore.Qt.PartiallyChecked: 1,
                        QtCore.Qt.Unchecked: 2}
        counts = [0, 0, 0]
        if parent_item is not None:
            for i in range(parent_item.rowCount()):
                child_item = parent_item.child(i)
                if child_item.isCheckable():
                    counts[check_states[child_item.checkState()]] += 1
            if counts[0] and not counts[1] and not counts[2]:
                parent_item.setCheckState(QtCore.Qt.Checked)
            elif not counts[0] and not counts[1] and counts[2]:
                parent_item.setCheckState(QtCore.Qt.Unchecked)
            else:
                parent_item.setCheckState(QtCore.Qt.PartiallyChecked)

Solution

  • As you have pointed out only the classes that inherit from QObject can emit signals, QStandardItem is not a QObject and therefore generates that problem. The appropriate option is to use QStandardItemModel, for this we overwrite the setData() method and establish a logic to verify if the state has changed and then the QStandardItem is issued using the itemFromIndex() method that returns a QStandardItem given a QModelIndex.

    Example:

    from PySide2 import QtCore, QtGui, QtWidgets
    
    
    class StandardItemModel(QtGui.QStandardItemModel):
        checkStateChanged = QtCore.Signal(QtGui.QStandardItem)
    
        def setData(self, index, value, role=QtCore.Qt.EditRole):
            if role == QtCore.Qt.CheckStateRole:
                last_value = self.data(index, role)
            val = super(StandardItemModel, self).setData(index, value, role)
            if role == QtCore.Qt.CheckStateRole and val:
                current_value = self.data(index, role)
                if last_value != current_value:
                    it = self.itemFromIndex(index)
                    self.checkStateChanged.emit(it)
            return val
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super(MainWindow, self).__init__(parent)
            w = QtWidgets.QTreeView()
    
            model = StandardItemModel(w)
            w.setModel(model)
    
            model.checkStateChanged.connect(self.foo_slot)
    
            for i in range(4):
                top_level = QtGui.QStandardItem("{}".format(i))
                top_level.setCheckable(True)
                model.appendRow(top_level)
                for j in range(5):
                    it = QtGui.QStandardItem("{}-{}".format(i, j))
                    it.setCheckable(True)
                    top_level.appendRow(it)
            w.expandAll()
            self.setCentralWidget(w)
    
        @QtCore.Slot(QtGui.QStandardItem)
        def foo_slot(self, item):
            print(item.text(), item.checkState())
    
    
    if __name__ == '__main__':
        import sys
        app = QtWidgets.QApplication(sys.argv)
        w = MainWindow()
        w.show()
        sys.exit(app.exec_())