我正在尝试使QTreeView与QSortFilterProxyModel一起使用。我写了一个最小的工作示例(不幸的是,由于问题的复杂性,它并不是那么简单)。完整的代码是:
import logging
from PyQt5 import QtCore, QtWidgets
import sys
class DBObject:
def __init__(self, name, parent, children=None):
self.name = name
self.parent = parent
self.children = children or list()
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, root, parent=None):
super().__init__(parent)
self._root = root
def columnCount(self, parent=None, *args, **kwargs):
return 1
def rowCount(self, parent=None, *args, **kwargs):
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=None, *args, **kwargs):
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=None):
if not index.isValid():
return QtCore.QVariant()
item = index.internalPointer()
if role == QtCore.Qt.DisplayRole:
return item.name
else:
return QtCore.QVariant()
def flags(self, index):
if not index.isValid():
return QtCore.Qt.NoItemFlags
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable
def setData(self, index, value, role=None):
if not index.isValid():
return False
item = index.internalPointer()
if role == QtCore.Qt.EditRole:
item.name = value
self.dataChanged.emit(index, index, [role])
return True
else:
return False
def insertRows(self, row, count, parent):
self.beginInsertRows(parent, row, row + count - 1)
parentItem = parent.internalPointer()
for i in range(count):
parentItem.children.append(DBObject("new", parentItem))
self.endInsertRows()
self.layoutChanged.emit()
return True
class ProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, root, parent=None):
super().__init__(parent)
self._root = root
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, root):
super().__init__()
self._root = root
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._root, self)
self._proxyModel = ProxyModel(self._root, self)
self._proxyModel.setSourceModel(self._model)
self._treeView.setModel(self._proxyModel)
self._treeView.expandAll()
button = QtWidgets.QPushButton("Add")
layout.addWidget(button)
button.clicked.connect(self._Clicked)
def _Clicked(self):
self._model.insertRow(len(self._root.children), self._model.index(0, 0, QtCore.QModelIndex()))
self._model.layoutChanged.emit()
self._treeView.expandAll()
def main():
root = DBObject("root", None)
items = ["foo", "bar", "baz"]
for x in items:
child = DBObject(x + "0", root)
root.children.append(child)
for y in items:
child.children.append(DBObject(y + "1", child))
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow(root)
mainWindow.show()
app.exec_()
if __name__ == "__main__":
main()
到目前为止,我只对显示可用数据感兴趣,甚至可以选择甚至编辑显示的数据。但是,现在我也想向模型中添加数据,但是我没有这样做。
为了添加数据,我需要重载下面的insertRows方法,对此我有几个问题:
def insertRows(self, row, count, parent):
self.beginInsertRows(parent, row, row + count - 1)
parentItem = parent.internalPointer()
for i in range(count):
parentItem.children.append(DBObject("new", parentItem))
self.endInsertRows()
self.layoutChanged.emit()
return True
当我开始在树形视图中选择其他项目并添加新数据时,就会出现问题。崩溃的范围从分段错误到QSortFilterProxyModel: index from wrong model passed to mapFromSource
。
我从模型测试套件中发现here来填充模型,并且一切正常。在此示例中,我使用的QSortFilterProxyModel不执行任何过滤或排序,因此问题与此无关。
感谢任何帮助,提示或反馈。
不必发出layoutChanged信号。另一个错误是index()
方法将父级的默认参数设置为None而不是QModelIndex。我也认为不必要在不使用* args和** kwargs的方法中使用它们,这些方法已经具有预定义的行为和参数。
import logging
import sys
from PyQt5 import QtCore, QtWidgets
class DBObject:
def __init__(self, name, parent, children=None):
self.name = name
self.parent = parent
self.children = children or list()
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, root, parent=None):
super().__init__(parent)
self._root = root
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.DisplayRole):
if not index.isValid():
return QtCore.QVariant()
item = index.internalPointer()
if role == QtCore.Qt.DisplayRole:
return item.name
else:
return QtCore.QVariant()
def flags(self, index):
if not index.isValid():
return QtCore.Qt.NoItemFlags
return (
QtCore.Qt.ItemIsEnabled
| QtCore.Qt.ItemIsEditable
| QtCore.Qt.ItemIsSelectable
)
def setData(self, index, value, role=QtCore.Qt.EditRole):
if not index.isValid():
return False
item = index.internalPointer()
if role == QtCore.Qt.EditRole:
item.name = value
self.dataChanged.emit(index, index, [role])
return True
else:
return False
def insertRows(self, row, count, parent):
self.beginInsertRows(parent, row, row + count - 1)
parentItem = parent.internalPointer()
for i in range(count):
parentItem.children.append(DBObject("new", parentItem))
self.endInsertRows()
return True
class ProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, root, parent=None):
super().__init__(parent)
self._root = root
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, root):
super().__init__()
self._root = root
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._root, self)
self._proxyModel = ProxyModel(self._root, self)
self._proxyModel.setSourceModel(self._model)
self._treeView.setModel(self._proxyModel)
self._treeView.expandAll()
button = QtWidgets.QPushButton("Add")
layout.addWidget(button)
button.clicked.connect(self._Clicked)
def _Clicked(self):
self._model.insertRow(
len(self._root.children), self._model.index(0, 0, QtCore.QModelIndex())
)
self._treeView.expandAll()
def main():
root = DBObject("root", None)
items = ["foo", "bar", "baz"]
for x in items:
child = DBObject(x + "0", root)
root.children.append(child)
for y in items:
child.children.append(DBObject(y + "1", child))
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow(root)
mainWindow.show()
app.exec_()
if __name__ == "__main__":
main()