我有一个源模型
source_model = TokenModel(QAbstractListModel)
,我可以同时将其用于多个视图。它包含令牌列表_tokens: list[TokenClass]
。我也想在 QTreeView 中使用它。为此,我创建了一个TreeProxyModel(QAbstractProxyModel)
,但无法实现树结构的子元素的显示。是否可以使用代理模型将 QAbstractListModel 转换为 QTreeView?怎么办?
import typing
from PyQt6 import QtWidgets
from PyQt6.QtCore import QAbstractListModel, QModelIndex, Qt, QVariant, QAbstractProxyModel
class TokenClass:
def __init__(self, token: str, accounts: list[str]):
self.token: str = token
self.accounts: list[str] = accounts # Список счетов.
class TokenModel(QAbstractListModel):
def __init__(self, token_class_list: list[TokenClass]):
super().__init__() # __init__() QAbstractListModel.
self._tokens: list[TokenClass] = token_class_list
def rowCount(self, parent: QModelIndex = ...) -> int:
return len(self._tokens)
def data(self, index: QModelIndex, role: int = ...) -> typing.Any:
if role == Qt.ItemDataRole.DisplayRole:
token_class: TokenClass = self._tokens[index.row()]
return QVariant(token_class.token)
else:
return QVariant()
def getTokenClass(self, row: int) -> TokenClass:
if 0 <= row < self.rowCount():
return self._tokens[row]
else:
raise ValueError("Invalid row value in getTokenClass() ({0})!".format(row))
class AccountItem:
def __init__(self, parent: QModelIndex, account: str):
self._account: str = account
self._parent: QModelIndex = parent
def parent(self) -> QModelIndex:
return self._parent
def data(self) -> str:
return self._account
class TreeProxyModel(QAbstractProxyModel):
def rowCount(self, parent: QModelIndex = ...) -> int:
if parent.isValid():
token: TokenClass = parent.internalPointer()
return len(token.accounts)
else:
return self.sourceModel().rowCount()
def columnCount(self, parent: QModelIndex = ...) -> int:
return 1
def data(self, index: QModelIndex, role: int = ...) -> typing.Any:
index_item: TokenClass | AccountItem = index.internalPointer()
if role == Qt.ItemDataRole.DisplayRole:
if type(index_item) == TokenClass:
return index_item.token
elif type(index_item) == AccountItem:
return index_item.data()
def index(self, row: int, column: int, parent: QModelIndex = ...) -> QModelIndex:
if parent.isValid():
token: TokenClass = parent.internalPointer()
account: str = token.accounts[row]
return self.createIndex(row, column, AccountItem(parent, account))
else:
token: TokenClass = self.sourceModel().getTokenClass(row)
return self.createIndex(row, column, token)
def parent(self, child: QModelIndex) -> QModelIndex:
if child.isValid():
data: TokenClass | AccountItem = child.internalPointer()
if type(data) == TokenClass:
return QModelIndex()
elif type(data) == AccountItem:
return data.parent()
else:
raise TypeError('Invalid element type: Type: {0}, Value: {1}!'.format(type(data), data))
else: # Если индекс child недействителен, то child - это счёт.
return QModelIndex()
def mapFromSource(self, sourceIndex: QModelIndex) -> QModelIndex:
return self.index(sourceIndex.row(), 0, QModelIndex())
def mapToSource(self, proxyIndex: QModelIndex) -> QModelIndex:
parent: QModelIndex = proxyIndex.parent()
if parent.isValid():
return QModelIndex()
else:
return self.sourceModel().index(proxyIndex.row(), 0, QModelIndex())
class Form(QtWidgets.QMainWindow):
def __init__(self, tokens: list[TokenClass]):
super().__init__() # __init__() QMainWindow.
self.centralwidget = QtWidgets.QWidget(self)
self.main_verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.treeView_tokens = QtWidgets.QTreeView(self.centralwidget)
self.main_verticalLayout.addWidget(self.treeView_tokens)
self.setCentralWidget(self.centralwidget)
source_model: TokenModel = TokenModel(tokens)
proxy_model: TreeProxyModel = TreeProxyModel()
proxy_model.setSourceModel(source_model)
self.treeView_tokens.setModel(proxy_model)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
token1: TokenClass = TokenClass('token1', ['account1', 'account2', 'account3'])
token2: TokenClass = TokenClass('token2', [])
token3: TokenClass = TokenClass('token3', ['account1'])
tokens: list[TokenClass] = [token1, token2, token3]
window = Form(tokens)
window.show()
sys.exit(app.exec())
我成功实现了想要的数据显示。为此,我将
QAbstractListModel
替换为 QAbstractItemModel
,将 QAbstractProxyModel
替换为 QAbstractItemModel
。将 QAbstractListModel
替换为 QAbstractItemModel
允许我在令牌左侧显示披露图标,但披露本身会因错误而终止程序。将 QAbstractProxyModel
替换为 QAbstractItemModel
消除了此错误。我必须使用 QAbstractItemModel
作为 TokenModel
的代理模型。这不是我想要的,我必须妥协才能得到结果。为了让 TreeProxyModel
对源数据的变化做出反应,我使用了 dataChanged
信号。
from __future__ import annotations
import typing
from PyQt6 import QtWidgets
from PyQt6.QtCore import QModelIndex, Qt, QVariant, QAbstractItemModel
class TokenClass:
def __init__(self, token: str, accounts: list[str]):
self.token: str = token
self.accounts: list[str] = accounts
class TokenModel(QAbstractItemModel):
def __init__(self, token_class_list: list[TokenClass]):
super().__init__() # __init__() QAbstractListModel.
self._tokens: list[TokenClass] = token_class_list
def rowCount(self, parent: QModelIndex = ...) -> int:
return len(self._tokens)
def columnCount(self, parent: QModelIndex = ...) -> int:
return 1
def index(self, row: int, column: int, parent: QModelIndex = ...) -> QModelIndex:
return self.createIndex(row, column)
def data(self, index: QModelIndex, role: int = ...) -> typing.Any:
if role == Qt.ItemDataRole.DisplayRole:
token_class: TokenClass = self._tokens[index.row()]
return QVariant(token_class.token)
else:
return QVariant()
def getTokens(self) -> list[TokenClass]:
return self._tokens
def getTokenClass(self, row: int) -> TokenClass:
if 0 <= row < self.rowCount():
return self._tokens[row]
else:
raise ValueError('Invalid row value in getTokenClass() ({0})!'.format(row))
class TreeItem:
def __init__(self, parent: TreeItem | None, data, children: list[TreeItem], row: int):
self._parent: TreeItem | None = parent
self.data = data
self._children: list[TreeItem] = children
self._row: int = row
def parent(self) -> TreeItem | None:
return self._parent
def setChildren(self, children: list[TreeItem]):
self._children = children
def childrenCount(self) -> int:
return len(self._children)
def child(self, row: int) -> TreeItem | None:
if 0 <= row < self.childrenCount():
return self._children[row]
else:
return None
def row(self) -> int:
return self._row
class TreeProxyModel(QAbstractItemModel):
def __init__(self, sourceModel: TokenModel):
super().__init__() # __init__() QAbstractProxyModel.
self._root_item: TreeItem = TreeItem(None, None, [], 0)
self._source_model: TokenModel = sourceModel
self._setTokens()
self._source_model.dataChanged.connect(self._setTokens)
def _setTokens(self):
self.beginResetModel()
token_list: list[TreeItem] = []
for row, token in enumerate(self._source_model.getTokens()):
token_item: TreeItem = TreeItem(self._root_item, token.token, [], row)
token_item.setChildren([TreeItem(token_item, account, [], j) for j, account in enumerate(token.accounts)])
token_list.append(token_item)
self._root_item.setChildren(token_list)
self.endResetModel()
def rowCount(self, parent: QModelIndex = ...) -> int:
if parent.column() > 0: return 0
if parent.isValid():
tree_item: TreeItem = parent.internalPointer()
assert type(tree_item) == TreeItem
else:
tree_item: TreeItem = self._root_item
return tree_item.childrenCount()
def columnCount(self, parent: QModelIndex = ...) -> int:
return 1
def data(self, index: QModelIndex, role: int = ...) -> typing.Any:
tree_item: TreeItem = index.internalPointer()
assert type(tree_item) == TreeItem
if role == Qt.ItemDataRole.DisplayRole:
return tree_item.data
def index(self, row: int, column: int, parent: QModelIndex = ...) -> QModelIndex:
if parent.isValid():
token_item: TreeItem = parent.internalPointer()
assert type(token_item) == TreeItem and token_item.parent() == self._root_item
account_item: TreeItem | None = token_item.child(row)
if account_item is None:
return QModelIndex()
else:
return self.createIndex(row, column, account_item)
else:
token_item: TreeItem | None = self._root_item.child(row)
if token_item is None:
return QModelIndex()
else:
return self.createIndex(row, column, token_item)
def parent(self, child: QModelIndex) -> QModelIndex:
if child.isValid():
tree_item: TreeItem = child.internalPointer()
assert type(tree_item) == TreeItem
parent_item: TreeItem | None = tree_item.parent()
if tree_item.parent() is None:
return QModelIndex()
elif parent_item == self._root_item:
return QModelIndex()
else:
return self.createIndex(parent_item.row(), 0, parent_item)
else:
return QModelIndex()
class Form(QtWidgets.QMainWindow):
def __init__(self, token_list: list[TokenClass]):
super().__init__() # __init__() QMainWindow.
self.centralwidget = QtWidgets.QWidget(self)
self.main_verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.treeView_tokens = QtWidgets.QTreeView(self.centralwidget)
self.main_verticalLayout.addWidget(self.treeView_tokens)
self.setCentralWidget(self.centralwidget)
source_model: TokenModel = TokenModel(token_list)
proxy_model: TreeProxyModel = TreeProxyModel(source_model)
self.treeView_tokens.setModel(proxy_model)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
token1: TokenClass = TokenClass('token1', ['account1', 'account2', 'account3'])
token2: TokenClass = TokenClass('token2', [])
token3: TokenClass = TokenClass('token3', ['account1'])
tokens: list[TokenClass] = [token1, token2, token3]
window = Form(tokens)
window.show()
sys.exit(app.exec())