python - 如何在 QTreeView 中为具有不同层次列数的模型显示正确的列数
问题描述
在下面的代码示例中,我使用一组顶级项目填充项目模型,其中包含可以使用 QTreeView 查看和编辑的键值属性。
查看QAbstractItemModel::columnCount的 Qt 文档,它说这应该返回给定父级的子级的列数,这意味着这应该是一个分层的依赖属性。
但是,使用下面的代码,如果我将列数作为分层依赖属性返回(在这种情况下root->children
有 1 列,root->child->children
有 2 列),那么视图将只显示 1 列。
打印node.columnCount()
(参见代码)实际上将显示Item
类节点在展开其中一项后实际上返回 columnCount = 2。
如果我总是为model.columnCount
函数返回 2,那么视图将正确显示两列。
无论层次结构如何,这是否需要始终在视图中返回所需的列数,或者我只是做错了什么,如果是这样怎么办?为子级具有不同列数的父级返回列数只是为了使视图正常工作,感觉一定是错误的。
import sys
import typing
from PyQt5 import QtCore, QtWidgets
class Node:
def __init__(self, parent=None):
self.parent = parent # type: Node
self.name : str
def children(self) -> list:
return None
def hasChildren(self):
return bool(self.children())
def getData(self, index: QtCore.QModelIndex):
if index.column() == 0:
return self.name
def setData(self, val, index: QtCore.QModelIndex):
if index.column() == 0:
self.name = val
def columnCount(self):
return 1
def rowCount(self):
children = self.children()
return 0 if not children else len(children)
def flags(self, index: QtCore.QModelIndex):
if index.column() == 0:
return (QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable)
else:
return QtCore.Qt.NoItemFlags
class Property(Node):
def __init__(self, parent, label, value):
super().__init__(parent)
self.label = label
self.value = value
def getData(self, index: QtCore.QModelIndex):
col = index.column()
if col == 0:
return self.label
elif col == 1:
return self.value
def setData(self, val, index: QtCore.QModelIndex):
if index.column() == 1:
self.value = val
def flags(self, index: QtCore.QModelIndex):
col = index.column()
if col == 0:
return QtCore.Qt.ItemIsEnabled
elif col == 1:
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEditable
class Item(Node):
def __init__(self, parent):
super().__init__(parent)
self.name = 'Item'
self.p1 = Property(self, 'string', 'text')
self.p2 = Property(self, 'float', 1.2)
def children(self):
return [self.p1, self.p2]
def columnCount(self):
return 2
class Root(Node):
def __init__(self):
super().__init__(parent=None)
self._children = list()
def children(self):
return self._children
class Model(QtCore.QAbstractItemModel):
def __init__(self):
super().__init__()
self.root = Root()
def index(self, row: int, column: int, parent: QtCore.QModelIndex = ...) -> QtCore.QModelIndex:
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
node = parent.internalPointer() if parent.isValid() else self.root
if node.children:
return self.createIndex(row, column, node.children()[row])
else:
return QtCore.QModelIndex()
def parent(self, child: QtCore.QModelIndex) -> QtCore.QModelIndex:
if not child.isValid():
return QtCore.QModelIndex()
node = child.internalPointer() # type: Node
if node.parent and node.parent.parent:
row = node.parent.parent.children().index(node.parent)
return self.createIndex(row, 0, node.parent)
else:
return QtCore.QModelIndex()
def rowCount(self, parent: QtCore.QModelIndex = ...) -> int:
node = parent.internalPointer() if parent.isValid() else self.root
children = node.children()
return len(children) if children else 0
def columnCount(self, parent: QtCore.QModelIndex = ...) -> int:
node = parent.internalPointer() if parent.isValid() else self.root
print(f'{node.__class__.__name__} column count: ', node.columnCount()) # shows that column count 2 is returned, when items are expanded
# return 2 # 2nd column only shows up if I just always return 2
return node.columnCount() # view only shows 1 columns
def hasChildren(self, parent: QtCore.QModelIndex = ...) -> bool:
node = parent.internalPointer() if parent.isValid() else self.root
return node.hasChildren()
def data(self, index: QtCore.QModelIndex, role: int = ...):
if index.isValid() and role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
node = index.internalPointer() # type: Node
return node.getData(index)
else:
return None
def setData(self, index: QtCore.QModelIndex, value: typing.Any, role: int = ...) -> bool:
if role in (QtCore.Qt.EditRole,):
node = index.internalPointer() # type: Node
node.setData(value, index)
self.dataChanged.emit(index, index)
return True
else:
return False
def flags(self, index: QtCore.QModelIndex):
node = index.internalPointer() if index.isValid() else self.root
return node.flags(index)
def appendRow(self, item):
row = len(self.root.children())
self.beginInsertRows(QtCore.QModelIndex(), row, row)
self.root.children().append(item)
self.endInsertRows()
class TreeView(QtWidgets.QTreeView):
def __init__(self, parent=None):
super(TreeView, self).__init__(parent)
self._model = Model()
self.setModel(self._model)
self.setSelectionMode(self.ExtendedSelection)
# self.setDropIndicatorShown(False)
self.setEditTriggers(self.DoubleClicked | self.SelectedClicked | self.EditKeyPressed)
def model(self) -> Model:
return self._model
sys.excepthook = sys.__excepthook__
app = QtWidgets.QApplication(sys.argv)
widget = TreeView()
model = widget.model()
for i in range(2):
model.appendRow(Item(model.root))
widget.show()
widget.setAttribute(QtCore.Qt.WA_DeleteOnClose)
sys.exit(app.exec_())
解决方案
似乎文档不清楚并且与实现不完全匹配,在实现中视图中的列数取决于水平QHeaderView
,水平QHeaderView
使用作为不可见项的根的列数,即列数应该由 给出Root()
,并且由于Root()
不会覆盖columnCount()
它,因此默认情况下它的值为 1(尽管对我来说columnCount()
Node 必须为 0 并且children()
必须返回一个空列表),因此解决方案在根columnCount()
。
import sys
import typing
from PyQt5 import QtCore, QtWidgets
class Node:
def __init__(self, parent=None):
self.parent = parent # type: Node
self.name : str
def children(self) -> list:
return list()
def hasChildren(self):
return bool(self.children())
def getData(self, index: QtCore.QModelIndex):
if index.column() == 0:
return self.name
def setData(self, val, index: QtCore.QModelIndex):
if index.column() == 0:
self.name = val
def columnCount(self):
return 0
def rowCount(self):
children = self.children()
return len(children)
def flags(self, index: QtCore.QModelIndex):
if index.column() == 0:
return (QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable)
else:
return QtCore.Qt.NoItemFlags
class Property(Node):
def __init__(self, parent, label, value):
super().__init__(parent)
self.label = label
self.value = value
def getData(self, index: QtCore.QModelIndex):
col = index.column()
if col == 0:
return self.label
elif col == 1:
return self.value
def setData(self, val, index: QtCore.QModelIndex):
if index.column() == 1:
self.value = val
def flags(self, index: QtCore.QModelIndex):
col = index.column()
if col == 0:
return QtCore.Qt.ItemIsEnabled
elif col == 1:
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEditable
def columnCount(self):
return 1
class Item(Node):
def __init__(self, parent):
super().__init__(parent)
self.name = 'Item'
self.p1 = Property(self, 'string', 'text')
self.p2 = Property(self, 'float', 1.2)
def children(self):
return [self.p1, self.p2]
def columnCount(self):
return 2
class Root(Node):
def __init__(self):
super().__init__(parent=None)
self._children = list()
def children(self):
return self._children
def columnCount(self):
return 2
class Model(QtCore.QAbstractItemModel):
def __init__(self):
super().__init__()
self.root = Root()
def index(self, row: int, column: int, parent: QtCore.QModelIndex = ...) -> QtCore.QModelIndex:
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
node = parent.internalPointer() if parent.isValid() else self.root
if node.children:
return self.createIndex(row, column, node.children()[row])
else:
return QtCore.QModelIndex()
def parent(self, child: QtCore.QModelIndex) -> QtCore.QModelIndex:
if not child.isValid():
return QtCore.QModelIndex()
node = child.internalPointer() # type: Node
if node.parent and node.parent.parent:
row = node.parent.parent.children().index(node.parent)
return self.createIndex(row, 0, node.parent)
else:
return QtCore.QModelIndex()
def rowCount(self, parent: QtCore.QModelIndex = ...) -> int:
node = parent.internalPointer() if parent.isValid() else self.root
children = node.children()
return len(children) if children else 0
def columnCount(self, parent: QtCore.QModelIndex = ...) -> int:
node = parent.internalPointer() if parent.isValid() else self.root
print(f'{node.__class__.__name__} column count: ', node.columnCount()) # shows that column count 2 is returned, when items are expanded
# return 2 # 2nd column only shows up if I just always return 2
return node.columnCount() # view only shows 1 columns
def hasChildren(self, parent: QtCore.QModelIndex = ...) -> bool:
node = parent.internalPointer() if parent.isValid() else self.root
return node.hasChildren()
def data(self, index: QtCore.QModelIndex, role: int = ...):
if index.isValid() and role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
node = index.internalPointer() # type: Node
return node.getData(index)
else:
return None
def setData(self, index: QtCore.QModelIndex, value: typing.Any, role: int = ...) -> bool:
if role in (QtCore.Qt.EditRole,):
node = index.internalPointer() # type: Node
node.setData(value, index)
self.dataChanged.emit(index, index)
return True
else:
return False
def flags(self, index: QtCore.QModelIndex):
node = index.internalPointer() if index.isValid() else self.root
return node.flags(index)
def appendRow(self, item):
row = len(self.root.children())
self.beginInsertRows(QtCore.QModelIndex(), row, row)
self.root.children().append(item)
self.endInsertRows()
class TreeView(QtWidgets.QTreeView):
def __init__(self, parent=None):
super(TreeView, self).__init__(parent)
self._model = Model()
self.setModel(self._model)
self.setSelectionMode(self.ExtendedSelection)
# self.setDropIndicatorShown(False)
self.setEditTriggers(self.DoubleClicked | self.SelectedClicked | self.EditKeyPressed)
def model(self) -> Model:
return self._model
sys.excepthook = sys.__excepthook__
app = QtWidgets.QApplication(sys.argv)
widget = TreeView()
model = widget.model()
for i in range(2):
model.appendRow(Item(model.root))
widget.show()
widget.setAttribute(QtCore.Qt.WA_DeleteOnClose)
sys.exit(app.exec_())
推荐阅读
- xamarin - 如何在 Akavache 中订阅新价值?
- python - 从主机名中提取 AWS AZ
- c# - Dapper QueryAsync,返回一个列表
- html - 如何让这个简单的 css-grid 布局在 IE11 中工作
- c++ - 如何重载由日期和月份组成的对象的前置和后置增量运算符,以打印为 std::string?
- node.js - 在每个区域上创建具有特定权限的 MongoDB userSchema
- c# - 如何在 .NET Core 中自动发送 HttpClient get 方法
- android - 允许一个参数使用不同的类型,例如 String 和 StringRes int 的总和类型
- javascript - 如何从输入框中添加新的数组元素并将其显示在列表中?
- javascript - 替换url后如何运行