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。
在这种情况下,我为演示选择了第一个选项:
main.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、C++)- Linux