Pyside2/6 QColumnView 似乎坏了,相同的模型适用于 QTreeView

Pyside2/6 QColumnView seems broken, same model works on QTreeView

提问人:Radek Wysocki 提问时间:11/2/2023 最后编辑:Radek Wysocki 更新时间:11/2/2023 访问量:36

问:

我正在开发一个应用程序,它涉及显示实体的分层结构。这些实体存储在一个无法完整获取的大型数据库中,因此我正在实现延迟加载,这意味着只有在用户展开父项时才会查询实体。

我遇到的问题是,当我单击一个实体时,我可以看到它正在控制台中加载,但是在我单击另一个项目然后返回到原始项目之前,UI 不会更新。有趣的是,当我用 QTreeView 替换 QColumnView 时,一切都按预期工作。

我怀疑这可能是 PySide 或 Qt 中的一个错误。但是,我愿意接受我可能忽略某些东西的可能性。我尝试发出不同的信号来指示我已更新该区域,但我的尝试都没有成功。我的代码目前使用的是 PySide2,但我也用 PySide6 对其进行了测试(只需将导入和 exec_() 替换为 exec()),问题仍然存在。任何见解或建议将不胜感激。

--编辑--

我之所以以这种方式实现加载,而不是 DirPathModel 处理它的方式,是因为我特别想只加载我单击的实体,而不是加载所有可见的实体。

DirPathModel 示例

--编辑结束--

我的代码:

# !/usr/bin/env python
# coding=utf-8

import logging
import time
from dataclasses import dataclass
from typing import Union, Any, List

import PySide2.QtCore
from PySide2.QtCore import QModelIndex, Qt
from PySide2.QtGui import QStandardItemModel, QStandardItem
from PySide2.QtWidgets import QColumnView, QApplication


class DatabaseConnector:
    """This queries database for projects and entities"""

    def get_root_data(self) -> List[dict]:
        time.sleep(1)  # Emulate query to server, it takes time
        return [
            {
                "id": "1",
                "type": "Project",
                "name": "Some project1"
            },
            {
                "id": "2",
                "type": "Project",
                "name": "Some project2"
            },
            {
                "id": "3",
                "type": "Project",
                "name": "Some project3"
            },
        ]

    def expand(self, f_id: str, f_type: str) -> List[dict]:
        time.sleep(1)  # Emulate query to server, it takes time
        # Normally this would not return get_root_data, but for simplicity of example it does.
        return self.get_root_data()


@dataclass
class Entity:
    id: str
    type: str


class BaseModel(QStandardItemModel):
    """Base model for entity selector"""

    def __init__(self) -> None:
        super().__init__()
        self.data_provider = DatabaseConnector()
        root_data = self.data_provider.get_root_data()
        for data in root_data:
            project_item = self._create_child(data)
            self.invisibleRootItem().appendRow(project_item)

    def _create_child(self, data) -> QStandardItem:
        """Creates a child item with a "Loading..." child."""
        entity = Entity(data['id'], data['type'])
        child_item = QStandardItem()
        child_item.setText(data['name'])
        child_item.setData(entity, Qt.ItemDataRole.UserRole)

        # placeholder child because if child_item is not empty a call to data() will be made
        loading_child = QStandardItem()
        loading_child.setText("Loading...")
        child_item.appendRow(loading_child)

        return child_item

    def data(self, index: Union[PySide2.QtCore.QModelIndex, PySide2.QtCore.QPersistentModelIndex],
             role: int = ...) -> Any:

        data = super().data(index, role)
        # We catch the placeholder "Loading..." child, which means we have to
        #  populate the parent entity.
        if role == Qt.ItemDataRole.DisplayRole and data == "Loading...":
            parent_index = index.parent()
            if not parent_index.isValid():
                return data

            logging.warning("Filling")
            self._fill_parent(parent_index)

        return data

    def _fill_parent(self, parent: QModelIndex) -> None:
        """Fills the parent entity with children"""
        parent_item = self.itemFromIndex(parent)
        entity: Entity = parent_item.data(Qt.ItemDataRole.UserRole)

        # Remove the placeholder
        while parent_item.rowCount() > 0:
            parent_item.removeRow(0)

        # Ask the server for children
        children_data = self.data_provider.expand(entity.id, entity.type)
        print("Data from server", children_data)

        # Insert the children
        for child in children_data:
            print(f"Appending {child['name']}")
            child_item = self._create_child(child)
            parent_item.appendRow(child_item)


if __name__ == '__main__':
    app = QApplication()
    model = BaseModel()
    entity_selector = QColumnView()
    entity_selector.setModel(model)
    entity_selector.resize(800, 200)
    entity_selector.show()
    app.exec_()
python qt 模型 延迟加载 pyside

评论

3赞 musicamante 11/3/2023
不应在视图访问项目时删除项目:调用时,视图仍期望从中获取结果,但在此期间您实际上是在销毁索引。如果更改为 ,它将按预期工作。尽管如此,这还是不太可接受的,因为 UI 永远不应该阻塞:一个正确的实现应该使用一个工作器 QThread,它将在父级显示其内容时查询服务器(除非已经进行了查询),通过最终填充模型的信号返回结果。data()QTimer.singleShot(0, lambda: self._fill_parent(parent_index))
1赞 relent95 11/3/2023
除了删除项目外,请勿在 .尽管它目前可能有效,但将来可能会被破坏。QStandardItemModel::data()
0赞 Radek Wysocki 11/3/2023
谢谢!有没有一种方法可以确定用户是否点击了某个项目(表明他们打算扩展它),而不依赖额外的信号和?这样,程序员只需调用 view.setModel() 即可,无需任何额外步骤。QStandardItemModel::data()

答: 暂无答案