QT/QML 数据模型

Posted

技术标签:

【中文标题】QT/QML 数据模型【英文标题】:QT/QML Data Model 【发布时间】:2021-06-14 12:05:15 【问题描述】:

我想创建一个具有如下结构的 Qt 数据模型,以便在 Python 和 QML 中使用。如果在 Python 或 QML 中更改、添加或删除任何值或键,我需要在另一端(QML 或 Python)更新这些值。理想情况下,这将是 ListView 中使用的模型,我只会显示 ListView 的某些字段。但我会使用这个数据模型来存储我的所有信息。当在 Python 中对内存执行测试时,我想将该测试日志信息写入此数据模型并在 QML 中显示。我已经阅读了 QAbstractListModel,但我不确定我是否可以制作嵌套对象或列表以及它们是否会自动更新。

System: 
    Processor: 
        name: 'Processor',
        description: 'Intel i7 6600k',
        iconSource: '/resources/images/chip.svg',
        progressValue: 100,
        pageSource: '/resources/qml/Processor.qml',
        details: 
            "badge": "Intel® Core i5 processor",
            "cache": "6144 KB",
            "clock": "4200000"
        
        testLog: [
            'Starting Cpu Test',
            'Detected Intel CPU',
            'Performing intense calculations',
            'Processing calculations still',
            'Cleaning up',
            'Test Passed'
        ]
        
    Memory: 
        name: 'Memory',
        description: 'Kingston 16GB DDR3',
        iconSource: '/resources/images/ram.svg',
        progressValue: 50,
        pageSource: '/resources/qml/Processor.qml',
        details: 
            "device_locator_string": "ChannelB-DIMM1",
            "device_set": 0,
            "error_handle": 65534,
            "extended_size": 0,
            "form_factor": "Unknown"
        ,
        testLog: [
            'Starting Memory Test',
            'Detected 2 x RAM modules',
            'Performing intense calculations',
            'Processing calculations still',
            'Cleaning up',
            'Test Failed'
        ]
    

【问题讨论】:

QAbstractItemModel 适用于树状结构。您需要实现parent 函数(至少在C++ 中,不确定Python,但应该给您一个想法) @Amfasis 你知道我是否可以将 QAbstractItemModel 输入到 ListView 中吗?不是只有 QAbstractListModel 和 QAbstractTableModel 可以与 ListView 一起使用吗? 不,您绝对可以将 QAbstractItemModel 的任何后代提供给 Qt 组件(ListView、GridView、Repeater 等)中的大多数模型属性,请参阅doc for C++ 【参考方案1】:

在这种情况下有几个选项,例如:

根据您通过角色提供属性的 QAbstractItemModel 创建模型。

创建一个具有所需属性的 QObject 设备作为 qproperties,并通过与来自另一个 QObject 的信号相关联的 qproperty 公开它,QObject 设备列表并将该列表用作模型。

将模型创建为 QAbstractListModel(或 QStandardItemModel)并通过角色公开 QObject。

创建一个通过 ListProperty 公开 QObjects Device 列表的 QObject。

在这种情况下,我为演示选择了第一个选项:

ma​​in.py

from dataclasses import dataclass
import sys
from typing import Callable

from PySide2.QtCore import (
    Property,
    QCoreApplication,
    QObject,
    QVariantAnimation,
    Qt,
    QUrl,
)
from PySide2.QtGui import QGuiApplication, QStandardItem, QStandardItemModel
from PySide2.QtQml import QQmlApplicationEngine


@dataclass
class item_property:
    role: int
    function: Callable = None

    def __call__(self, function):
        self.function = function
        return self


class item_property_impl(property):
    def __init__(self, role, function):
        super().__init__()
        self._role = role
        self._function = function

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        if hasattr(obj, "_initial"):
            obj.setData(self._function(obj), self._role)
            delattr(obj, "_initial")
        return obj.data(self._role)

    def __set__(self, obj, value):
        obj.setData(value, self._role)


class ItemMeta(type(QStandardItem), type):
    def __new__(cls, name, bases, attrs):
        for key in attrs.keys():
            attr = attrs[key]
            if not isinstance(attr, item_property):
                continue
            new_prop = item_property_impl(attr.role, attr.function)
            attrs[key] = new_prop
            if not hasattr(cls, "attrs"):
                cls._names = []
            cls._names.append(key)

        obj = super().__new__(cls, name, bases, attrs)
        return obj

    def __call__(cls, *args, **kw):
        obj = super().__call__(*args, **kw)
        obj._initial = True
        for key in cls._names:
            getattr(obj, key)
        return obj


class Item(QStandardItem, metaclass=ItemMeta):
    pass


keys = (b"name", b"description", b"icon", b"progress", b"source", b"details", b"log")
ROLES = (
    NAME_ROLE,
    DESCRIPTION_ROLE,
    ICON_ROLE,
    PROGRESS_ROLE,
    SOURCE_ROLE,
    DETAILS_ROLE,
    LOG_ROLE,
) = [Qt.UserRole + i for i, _ in enumerate(keys)]


class Device(Item):
    @item_property(role=NAME_ROLE)
    def name(self):
        return ""

    @item_property(role=DESCRIPTION_ROLE)
    def description(self):
        return ""

    @item_property(role=ICON_ROLE)
    def icon(self):
        return ""

    @item_property(role=PROGRESS_ROLE)
    def progress(self):
        return 0

    @item_property(role=SOURCE_ROLE)
    def source(self):
        return ""

    @item_property(role=DETAILS_ROLE)
    def details(self):
        return dict()

    @item_property(role=LOG_ROLE)
    def log(self):
        return list()


class DeviceManager(QObject):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._model = QStandardItemModel()
        self._model.setItemRoleNames(dict(zip(ROLES, keys)))

    def get_model(self):
        return self._model

    model = Property(QObject, fget=get_model, constant=True)

    def add_device(self, *, name, description, icon, progress, source, details, log):
        dev = Device()
        dev.name = name
        dev.description = description
        dev.icon = icon
        dev.progress = progress
        dev.source = source
        dev.details = details
        dev.log = log
        self.model.appendRow(dev)
        return dev


def main():
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()

    manager = DeviceManager()
    engine.rootContext().setContextProperty("device_manager", manager)

    url = QUrl("main.qml")

    def handle_object_created(obj, obj_url):
        if obj is None and url == obj_url:
            QCoreApplication.exit(-1)

    engine.objectCreated.connect(handle_object_created, Qt.QueuedConnection)
    engine.load(url)

    processor = manager.add_device(
        name="Processor",
        description="Intel i7 6600k",
        icon="/resources/images/chip.svg",
        progress=10,
        source="resources/qml/Processor.qml",
        details=
            "badge": "Intel® Core i5 processor",
            "cache": "6144 KB",
            "clock": "4200000",
        ,
        log=[
            "Starting Cpu Test",
            "Detected Intel CPU",
            "Performing intense calculations",
            "Processing calculations still",
            "Cleaning up",
            "Test Passed",
        ],
    )

    memory = manager.add_device(
        name="Memory",
        description="Kingston 16GB DDR3",
        icon="/resources/images/ram.svg",
        progress=50,
        source="resources/qml/Memory.qml",
        details=
            "device_locator_string": "ChannelB-DIMM1",
            "device_set": 0,
            "error_handle": 65534,
            "extended_size": 0,
            "form_factor": "Unknown",
        ,
        log=[
            "Starting Memory Test",
            "Detected 2 x RAM modules",
            "Performing intense calculations",
            "Processing calculations still",
            "Cleaning up",
            "Test Failed",
        ],
    )

    def update_progress(value):
        processor.progress = value

    animation = QVariantAnimation(
        startValue=processor.progress, endValue=100, duration=3 * 1000
    )
    animation.valueChanged.connect(update_progress)
    animation.start()

    ret = app.exec_()
    sys.exit(ret)


if __name__ == "__main__":
    main()

main.qml

import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow 
    id: root

    visible: true
    width: 400
    height: 400

    ListView 
        id: view

        property url currentSource: ""

        model: device_manager.model
        width: parent.width / 2
        height: parent.height
        spacing: 10
        clip: true
        flickableDirection: Flickable.VerticalFlick
        boundsBehavior: Flickable.StopAtBounds
        currentIndex: -1

        ScrollBar.vertical: ScrollBar 
        

        highlight: Rectangle 
            color: "lightsteelblue"
            radius: 5
        

        delegate: Rectangle 
            id: rect

            color: "transparent"
            border.color: ListView.isCurrentItem ? "red" : "green"
            height: column.height
            width: ListView.view.width

            Column 
                id: column

                Text 
                    text: model.name
                

                ProgressBar 
                    from: 0
                    to: 100
                    value: model.progress
                

                Label 
                    text: "Log:"
                    font.bold: true
                    font.pointSize: 15
                

                Text 
                    text: model.log.join("\n")
                

            

            MouseArea 
                anchors.fill: parent
                onClicked: 
                    rect.ListView.view.currentIndex = index;
                    rect.ListView.view.currentSource = model.source;
                
            

        

    

    Rectangle 
        x: view.width
        width: parent.width / 2
        height: parent.height
        color: "salmon"

        Loader 
            anchors.centerIn: parent
            source: view.currentSource
        

    


处理器.qml

import QtQuick 2.15
import QtQuick.Controls 2.15

Rectangle
    color: "red"
    width: 100
    height: 40
    Text
        text: "Processor"
        anchors.centerIn: parent
    

内存.qml

import QtQuick 2.15
import QtQuick.Controls 2.15

Rectangle
    color: "blue"
    width: 100
    height: 40
    Text
        text: "Memory"
        anchors.centerIn: parent
    

├── main.py
├── main.qml
└── resources
    └── qml
        ├── Memory.qml
        └── Processor.qml

【讨论】:

你提到的第一个选项是使用 QAbstractItemModel,你说你正在做一个演示?但我认为你的演示没有使用 QAbstractItemModel。 @Aaron QAbstractItemModel 是许多模型的基础,其中包括 QStandardItemModel,QStandardItemModel 的优势在于它已经实现了许多功能,从而节省了我的编程时间。你可以从头开始实现模型,但我很懒。 这太棒了,超级有用!我将在接下来的几天里研究这个并阅读文档。我会在这里问我的任何问题,再次感谢 Edwin! 我一直在研究你的答案。我将如何进行这项工作? memory.details['form_factor'] = 'DDR3' 或 memory.log.append('new log entry')。我知道我可以将它分配给一个变量并写入整个对象。但是我怎样才能修改代码以便能够直接在 Python 中操作这样的对象呢? 另外,当您在 device_manager 中声明模型属性时,为什么要使用 constant=True 而不是 notify?我认为只有在属性不可变时才使用“常量”。

以上是关于QT/QML 数据模型的主要内容,如果未能解决你的问题,请参考以下文章

QT QML - 从另一个类访问 qml 模型

嵌套模型/视图架构

如果数据库可访问,如何快速检查? (Qt、QML、C++)- Linux

如何分析“绑定循环”

Qt/QML:如何在 QML 中双向同步 ScrollView?

Qt/QML - 在 C++ 中注册 QML 类型会使 QML 代码不起作用