Search code examples
pythonpysideqtreeview

Check/Uncheck selected treeview items


How can i make it so when a user clicks on the checkbox within the selected items, it changes all the selected items in the treeview to match the state of the clicked checkbox? The item that is clicked.

As an example, when i click Jason, it should toggle all the other items selected to also be checked. However what currently happens instead, is Jason becomes the only item selected and checked.

Before clicking Jason:

enter image description here

Result currently after clicking Jason:

enter image description here

What i would like to happen:

enter image description here

import sys
import os
from PySide import QtGui, QtCore


class Example(QtGui.QDialog):
    def __init__(self, parent=None):
        super(Example, self).__init__(parent)
        self.resize(300, 300)
        self.setWindowTitle('Example')

        self.ui_items = QtGui.QTreeView()
        self.ui_items.sortByColumn(1, QtCore.Qt.AscendingOrder)
        self.ui_items.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
        self.ui_items.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
        self.ui_items.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
        self.ui_items.setModel(QtGui.QStandardItemModel())

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

        self.create_model()


    def create_model(self):
        model = self.ui_items.model()
        model.clear()
        model.setHorizontalHeaderLabels(['Items'])
        self.ui_items.sortByColumn(0, QtCore.Qt.AscendingOrder)

        data = {
            'Family 01': ['Amy', 'Kevin'],
            'Family 02': ['Sarah', 'Jason', 'Abby'],
            'Family 03': ['Michelle', 'Mike', 'Scott', 'Allie']
        }

        for k, v in data.items():
            root = []

            # family
            root_node = QtGui.QStandardItem()
            root_node.setData(k, role=QtCore.Qt.DisplayRole)
            root.append(root_node)

            # children
            for child in v:

                row = []
                row_node = QtGui.QStandardItem()
                row_node.setData(child, role=QtCore.Qt.DisplayRole)
                row_node.setCheckable(True)
                row.append(row_node)
                root_node.appendRow(row)

            model.appendRow(root)

        self.ui_items.expandAll()
        self.ui_items.resizeColumnToContents(0)


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


if __name__ == '__main__':
    main()

Solution

  • The first step is connecting to a signal that is emitted when the state of an item changes.

    This can be achieved by:

    model = self.ui_items.model()
    model.itemChanged.connect(self.handle_statechange)
    

    These lines have to be inserted somewhere after your setModel() command. handle_statechange is the name of a method that you will have to define:

    def handle_statechange(self, checked_item):
        state = checked_item.checkState()
        indices = self.ui_items.selectedIndexes()
        for idx in indices:
            item = idx.model().itemFromIndex(idx)
            item.setCheckState(state)
    

    Edit: Eyllanesc comments that clicking on a check box deselects all items besides the one with the clicked checkbox. That's the normal behavior, which you can avoid as a user by keeping the "Ctrl"-button clicked. I dont't know if and how you can override this standard behavior as a programmer.