4

How can I emit a signal when the checkbox of a treeview item is changed?

import sys
from PySide import QtGui, QtCore

class Browser(QtGui.QDialog):
    def __init__(self, parent=None):
        super(Browser, self).__init__(parent)

        self.initUI()

    def initUI(self):
        self.resize(200, 300)
        self.setWindowTitle('Assets')
        self.setModal(True)

        self.results = ""

        self.uiItems = QtGui.QTreeView()
        self.uiItems.setAlternatingRowColors(True)
        self.uiItems.setSortingEnabled(True)
        self.uiItems.sortByColumn(0, QtCore.Qt.AscendingOrder)
        self.uiItems.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
        self.uiItems.header().setResizeMode(QtGui.QHeaderView.ResizeToContents)

        grid = QtGui.QGridLayout()
        grid.setContentsMargins(0, 0, 0, 0)
        grid.addWidget(self.uiItems, 0, 0)
        self.setLayout(grid)

        self.show()
        self.create_model()

    def create_model(self):
        items = [
            'Cookie dough',
            'Hummus',
            'Spaghetti',
            'Dal makhani',
            'Chocolate whipped cream'
        ]

        model = QtGui.QStandardItemModel()
        model.setHorizontalHeaderLabels(['Name'])

        for item in items:
            model.insertRow(0)

            # Append object
            model.setData(model.index(0, 0), QtCore.Qt.Unchecked, role = QtCore.Qt.CheckStateRole)
            model.setData(model.index(0, 0), item)

            item = model.itemFromIndex(model.index(0,0))
            item.setCheckable(True)


        self.uiItems.setModel(model)


def main():
    app = QtGui.QApplication(sys.argv)
    ex = Browser()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
JokerMartini
  • 5,674
  • 9
  • 83
  • 193

2 Answers2

4

A major problem with using the itemChanged signal is that it doesn't tell you what changed. It would be so much more useful if it sent the specific role of the data that had changed.

As it is, there is always a danger of getting false positives from changes to other types of data that you are not interested in (there are fifteen pre-defined data roles, and any number of user-defined ones).

So a more robust solution would be to sub-class the model and emit a custom signal that specifically includes the role:

        model = StandardItemModel()
        ...

        self.uiItems.setModel(model)
        model.itemDataChanged.connect(self.handleItemDataChanged)

    def handleItemDataChanged(self, item, role):
        if role == QtCore.Qt.CheckStateRole:
            print(item.text(), item.checkState())


class StandardItemModel(QtGui.QStandardItemModel):
    itemDataChanged = QtCore.Signal(object, object)

    def setData(self, index, value, role=QtCore.Qt.EditRole):
        oldvalue = index.data(role)
        result = super(StandardItemModel, self).setData(index, value, role)
        if result and value != oldvalue:
            self.itemDataChanged.emit(self.itemFromIndex(index), role)
        return result
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
1

Use the itemChanged signal from the QStandardItemModel.

import sys
from PyQt4 import QtGui, QtCore

class Browser(QtGui.QDialog):
    def __init__(self, parent=None):
        super(Browser, self).__init__(parent)

        self.initUI()

    def initUI(self):
        self.resize(200, 300)
        self.setWindowTitle('Assets')
        self.setModal(True)

        self.results = ""

        self.uiItems = QtGui.QTreeView()
        self.uiItems.setAlternatingRowColors(True)
        self.uiItems.setSortingEnabled(True)
        self.uiItems.sortByColumn(0, QtCore.Qt.AscendingOrder)
        self.uiItems.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
        self.uiItems.header().setResizeMode(QtGui.QHeaderView.ResizeToContents)

        grid = QtGui.QGridLayout()
        grid.setContentsMargins(0, 0, 0, 0)
        grid.addWidget(self.uiItems, 0, 0)
        self.setLayout(grid)

        self.show()
        self.create_model()

    def create_model(self):
        items = [
            'Cookie dough',
            'Hummus',
            'Spaghetti',
            'Dal makhani',
            'Chocolate whipped cream'
        ]

        model = QtGui.QStandardItemModel()
        model.setHorizontalHeaderLabels(['Name'])

        for item in items:
            model.insertRow(0)

            # Append object
            model.setData(model.index(0, 0), QtCore.Qt.Unchecked, role = QtCore.Qt.CheckStateRole)
            model.setData(model.index(0, 0), item)

            item = model.itemFromIndex(model.index(0,0))
            item.setCheckable(True)

        # Added the following line.
        model.itemChanged.connect(self.test)
        self.uiItems.setModel(model)

    # Added the following method.
    def test(self, item):
        if item.text() == "Hummus":
            if item.checkState() == QtCore.Qt.Checked:
                # Hummus is selected
                # do something here
                pass
            else:
                # Hummus is deselected
                # do something here
                pass
        if item.text() == "Spaghetti":
            if item.checkState() == QtCore.Qt.Checked:
                # Spaghetti is selected
                # do something here
                pass
            else:
                # Spaghetti is deselected
                # do something here
                pass


def main():
    app = QtGui.QApplication(sys.argv)
    ex = Browser()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()
Dan-Dev
  • 8,957
  • 3
  • 38
  • 55
  • I ran into a problem with this though. I have a QTreeWidget, populated with QTreeWidgetItems, that then have other QTreeWidgetItems as children. When all of the children have their checkboxes turned off, it automatically unchecks the parent. However, this doesn't emit a signal. So I am able to capture the changing of the child widget itself, but when I query the parent's state, it hasn't been unchecked yet, as that appears to happen after the signal gets passed. Would you know of a way to catch this? – Jesse Sep 16 '20 at 07:49
  • Of course, right after posting this, I realize that I was looking to see if the parent was in either a checked, or unchecked state. But evidently, I missed a third option of "partially checked". Including that in the possible states seems to fix my issue. – Jesse Sep 16 '20 at 07:53