如何使用代理模型从qt中的源模型制作树模型?

问题描述 投票:0回答:1

我有一个源模型

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())

window

python qt pyqt qtreeview qabstractlistmodel
1个回答
0
投票

我成功实现了想要的数据显示。为此,我将

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())

window

© www.soinside.com 2019 - 2024. All rights reserved.