Reputation: 1441
Why are the checkboxes in my TreeView not checkable? I think there is an issue with the index() and parent() methods, but do not know how to fix it. setData() also never called...
Any help is really appreciated...
import logging
import sys
from PySide6 import QtCore, QtWidgets
from PySide6.QtCore import QSortFilterProxyModel, QPersistentModelIndex
class DBObject:
def __init__(self, name, parent, children=None, is_checkable=False):
self.name = name
self.parent = parent
self.children = children or list()
self.is_checkable = is_checkable
def __repr__(self):
return f"name: {self.name}, parent: {self.parent.name if self.parent is not None else '-'}"
class Model(QtCore.QAbstractItemModel):
def __init__(self, parent=None):
super().__init__(parent)
self._root = DBObject("root", None)
self.newData()
self.checks = {}
def checkState(self, index):
if index in self.checks.keys():
return self.checks[index]
else:
return QtCore.Qt.CheckState.Unchecked
def newData(self):
items = ["foo", "bar", "baz"]
for x in items:
child = DBObject(x + "0", self._root)
self._root.children.append(child)
for y in items:
child.children.append(DBObject(y + "1", child, None, True))
def columnCount(self, parent=QtCore.QModelIndex()):
return 1
def rowCount(self, parent=QtCore.QModelIndex()):
if not parent.isValid():
return 1
parentItem = parent.internalPointer()
rowCount = len(parentItem.children)
logging.info(f"rowCount({parentItem}): rowCount={rowCount}")
return rowCount
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
item = index.internalPointer()
parentItem = item.parent
logging.info(f"parent({item}): parent={parentItem}")
if parentItem is None:
return QtCore.QModelIndex()
else:
if parentItem.parent is None:
return self.createIndex(0, 0, parentItem)
else:
return self.createIndex(parentItem.parent.children.index(parentItem), 0, parentItem)
def index(self, row, column, parent=QtCore.QModelIndex()):
if not parent.isValid():
if row != 0 or column != 0:
return QtCore.QModelIndex()
else:
logging.info(f"index({row}, {column}, None): index={self._root}")
return self.createIndex(0, 0, self._root)
parentItem = parent.internalPointer()
if 0 <= row < len(parentItem.children):
logging.info(f"index({row}, {column}, {parentItem}): index={parentItem.children[row]}")
return self.createIndex(row, column, parentItem.children[row])
else:
logging.info(f"index({row}, {column}, {parentItem}): index=None")
return QtCore.QModelIndex()
def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole):
row = index.row()
col = index.column()
if not index.isValid():
return None
item = index.internalPointer()
if role == QtCore.Qt.ItemDataRole.CheckStateRole and col == 0 and item.is_checkable:
return QtCore.Qt.CheckState.Checked
if role == QtCore.Qt.ItemDataRole.DisplayRole:
return item.name
else:
return None
def setData(self, index, value, role=QtCore.Qt.ItemDataRole.EditRole):
if not index.isValid():
return False
if role == QtCore.Qt.ItemDataRole.CheckStateRole:
self.checks[QPersistentModelIndex(index)] = value
return True
return False
class ProxyModel(QSortFilterProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
self.setFilterKeyColumn(0)
self.setRecursiveFilteringEnabled(True)
def flags(self, index):
if not index.isValid():
return QtCore.Qt.ItemFlag.NoItemFlags
return (
QtCore.Qt.ItemFlag.ItemIsEnabled
| QtCore.Qt.ItemFlag.ItemIsSelectable)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setMinimumSize(640, 480)
centralWidget = QtWidgets.QWidget(self)
self.setCentralWidget(centralWidget)
layout = QtWidgets.QVBoxLayout(centralWidget)
self._treeView = QtWidgets.QTreeView(self)
layout.addWidget(self._treeView)
self._model = Model()
self._proxyModel = ProxyModel()
self._proxyModel.setSourceModel(self._model)
self._treeView.setModel(self._proxyModel)
# self._proxyModel.setFilterFixedString("bar1")
button = QtWidgets.QPushButton("Add")
layout.addWidget(button)
button.clicked.connect(self._Clicked)
def _Clicked(self):
self._model.newData()
self._treeView.expandAll()
def main():
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
app.exec()
if __name__ == "__main__":
main()
Upvotes: 1
Views: 281
Reputation: 48241
There are various issues with your code:
ItemIsUserCheckable
flag;flag()
in the proxy model (uselessly, by the way, since you're just returning the default flags of QAbstractItemModel);data()
always returns Checked
no matter its value;setData()
should always emit dataChanged
(as the documentation points out);Change the flag()
behavior of the source model, remove that of the proxy (or implement it properly, using the default implementation), and correct both data()
and setData()
.
class Model(QtCore.QAbstractItemModel):
def __init__(self, parent=None):
super().__init__(parent)
self._root = DBObject("root", None)
self.newData()
self.checks = {}
def newData(self):
items = ["foo", "bar", "baz"]
for x in items:
child = DBObject(x + "0", self._root)
self._root.children.append(child)
for y in items:
child.children.append(DBObject(y + "1", child, None, True))
def columnCount(self, parent=QtCore.QModelIndex()):
return 1
def flags(self, index):
flags = super().flags(index)
item = index.internalPointer()
if item and item.is_checkable:
flags |= QtCore.Qt.ItemIsUserCheckable
return flags
def rowCount(self, parent=QtCore.QModelIndex()):
if not parent.isValid():
return 1
return len(parent.internalPointer().children)
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
item = index.internalPointer()
parentItem = item.parent
if parentItem is None:
return QtCore.QModelIndex()
if parentItem.parent is None:
return self.createIndex(0, 0, parentItem)
else:
return self.createIndex(
parentItem.parent.children.index(parentItem), 0, parentItem)
def index(self, row, column, parent=QtCore.QModelIndex()):
if not parent.isValid():
if row != 0 or column != 0:
return QtCore.QModelIndex()
else:
return self.createIndex(0, 0, self._root)
parentItem = parent.internalPointer()
if 0 <= row < len(parentItem.children):
return self.createIndex(row, column, parentItem.children[row])
return QtCore.QModelIndex()
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return None
item = index.internalPointer()
col = index.column()
if role == QtCore.Qt.CheckStateRole and col == 0 and item.is_checkable:
return self.checks.get(QPersistentModelIndex(index), QtCore.Qt.Unchecked)
if role == QtCore.Qt.DisplayRole:
return item.name
def setData(self, index, value, role=QtCore.Qt.EditRole):
if not index.isValid():
return False
if role == QtCore.Qt.CheckStateRole:
pIndex = QPersistentModelIndex(index)
if self.checks.get(pIndex) != value:
self.checks[pIndex] = value
self.dataChanged.emit(index, index)
return True
return False
Also:
else:
blocks that are implicit in the function flow (especially if used to just return None
at the end of a function);Upvotes: 2