4

I implemented a cutom model for treeview for having checkboxes inside the treeview. If I check a parent node all child nodes should be chekced automatically. This basically works but der is a time lack between checking the parent node and updating the subnodes.

from PyQt4 import QtCore, QtGui
import sys

class Node(object):   
    def __init__(self, name, parent=None, checked=False):

        self._name = name
        self._children = []
        self._parent = parent
        self._checked = checked

        if parent is not None:
            parent.addChild(self)

    def addChild(self, child):
        self._children.append(child)

    def insertChild(self, position, child):
        if position < 0 or position > len(self._children):
            return False

        self._children.insert(position, child)
        child._parent = self
        return True

    def name(self):
        return self._name

    def checked(self):
        return self._checked

    def setChecked(self, state):
        self._checked = state

        for c in self._children:
            c.setChecked(state)

    def child(self, row):
        return self._children[row]

    def childCount(self):
        return len(self._children)

    def parent(self):
        return self._parent

    def row(self):
        if self._parent is not None:
            return self._parent._children.index(self)

class TreeModel(QtCore.QAbstractItemModel):
    def __init__(self, root, parent=None):
        super().__init__(parent)
        self._rootNode = root

    def rowCount(self, parent):
        if not parent.isValid():
            parentNode = self._rootNode
        else:
            parentNode = parent.internalPointer()

        return parentNode.childCount()

    def columnCount(self, parent):
        return 1

    def data(self, index, role):
        if not index.isValid():
            return None

        node = index.internalPointer()

        if role == QtCore.Qt.DisplayRole:
            if index.column() == 0:
                return node.name()
        if role == QtCore.Qt.CheckStateRole:
            if node.checked():
                return QtCore.Qt.Checked
            else:
                return QtCore.Qt.Unchecked

    def setData(self, index, value, role=QtCore.Qt.EditRole):

        if index.isValid():
            if role == QtCore.Qt.CheckStateRole:
                node = index.internalPointer()
                node.setChecked(not node.checked())
                return True
        return False

    def headerData(self, section, orientation, role):
        if role == QtCore.Qt.DisplayRole:
            return "Nodes"

    def flags(self, index):
        return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsUserCheckable

    def parent(self, index):
        node = self.getNode(index)
        parentNode = node.parent()

        if parentNode == self._rootNode:
            return QtCore.QModelIndex()
        return self.createIndex(parentNode.row(), 0, parentNode)

    def index(self, row, column, parent):
        parentNode = self.getNode(parent)
        childItem = parentNode.child(row)

        if childItem:
            return self.createIndex(row, column, childItem)
        else:
            return QtCore.QModelIndex()

    def getNode(self, index):
        if index.isValid():
            node = index.internalPointer()
            if node:
                return node

        return self._rootNode

    def removeRows(self, position, rows, parent=QtCore.QModelIndex()):

        parentNode = self.getNode(parent)
        self.beginRemoveRows(parent, position, position + rows - 1)

        for row in range(rows):
            success = parentNode.removeChild(position)

        self.endRemoveRows()

        return success

def main_simple():
    app = QtGui.QApplication(sys.argv)

    rootNode   = Node("Root")
    n1 = Node("Node1", rootNode)
    n2 = Node("Node2", rootNode)
    n3 = Node("Node3", rootNode)

    n1_1 = Node("Node1 1", n1)
    n1_2 = Node("Node1 2", n1)
    n1_3 = Node("Node1 3", n1)

    n2_1 = Node("Node2 1", n2)
    n2_2 = Node("Node2 2", n2)
    n2_3 = Node("Node2 3", n2)

    n3_1 = Node("Node3 1", n3)
    n3_2 = Node("Node3 2", n3)
    n3_3 = Node("Node3 3", n3)

    model = TreeModel(rootNode)

    treeView = QtGui.QTreeView()
    treeView.show()
    treeView.setModel(model)

    sys.exit(app.exec_())

if __name__ == '__main__':
    main_simple()

How can I avoid this lack so that the userinterface is more smoothly?

Razer
  • 7,843
  • 16
  • 55
  • 103

2 Answers2

5

The model should emit the dataChanged() signal for the children when they are toggled, so that the view can update their checkboxes instantly. According to the documentation, setData should also emit dataChanged for the item that was explicitly changed.

This should work:

def setData(self, index, value, role=QtCore.Qt.EditRole):
    if index.isValid():
        if role == QtCore.Qt.CheckStateRole:
            node = index.internalPointer()
            node.setChecked(not node.checked())
            self.dataChanged.emit(index, index)               
            self.emitDataChangedForChildren(index)
            return True
    return False

def emitDataChangedForChildren(self, index):
    count = self.rowCount(index)
    if count:            
        self.dataChanged.emit(index.child(0, 0), index.child(count-1, 0))
        for child in range(count):
            self.emitDataChangedForChildren(index.child(child, 0))
alexisdm
  • 29,448
  • 6
  • 64
  • 99
  • 1
    Note that although the OP only requested to refresh the clicked nodes *children*, combining checkboxes with `QAbstractItemModel` typically also requires updating the *parent* nodes, as long as they represent a kind of *logical and* of the children (especially you want to set every node to `Qt.PartiallyChecked` if its children have a mixed check state). In this situation it turned out for me that a mere refresh of the root node `self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())` works out just fine without the need of the answer's iterative approach. – flonk Apr 15 '20 at 12:44
0

As per flonks answer above, a non-iterative solution that worked for me is as follows:

C++: emit dataChanged(QModelIndex(), QModelIndex());

Python: self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())

This should update all items in the tree.

SteveEng
  • 331
  • 2
  • 6