QML模型基础架构
QML采用经典的Model-View-Delegate (MVD)架构来分离数据与界面,这与MVC模式类似但更加适合声明式UI开发。在这个架构中:
- Model:负责管理数据,可以是简单的整数,也可以是复杂的C++自定义模型
- View:负责显示数据,如ListView、GridView、TableView等
- Delegate:负责如何显示单个数据项,相当于模板
// 基本结构示例
ListView {
width: 200; height: 250
model: myModel // 数据模型
delegate: Rectangle { // 委托项
Text { text: model.display }
}
}
Qt的模型系统最大的特点是灵活性——几乎任何数据类型都可以作为模型使用。模型在QML中不仅是数据容器,还通过角色(roles)系统提供数据访问接口,委托可以通过这些角色访问模型中的数据项。
整数模型:最简单的模型类型
整数模型是QML中最简单的模型形式,它仅提供一个整数计数,用于生成重复的UI元素。
import QtQuick 2.15
import QtQuick.Controls 2.15
Rectangle {
width: 600; height: 400
ListView {
anchors.fill: parent
model: 5 // 整数作为模型
delegate: Rectangle {
width: parent.width
height: 30
required property int index
Text {
text: "我是第"+index + "个元素"
}
}
}
}
使用场景:
- 创建固定数量的相似组件
- 快速原型开发
- 不需要复杂数据绑定的简单界面
特点:
- 每个委托项自动获得index属性(从0开始)
- 没有额外的数据角色,仅提供索引值
- 性能高效,适合静态简单布局
高级用法:
可以与Repeater结合使用,创建网格布局:
Grid {
columns: 3
Repeater {
model: 9 // 创建3x3网格
Rectangle {
width: 50; height: 50
color: index%2 ? "red" : "blue"
}
}
}
整数模型虽然简单,但在许多不需要复杂数据的场景下非常实用,能够避免创建不必要的复杂模型,注意:整数模型中的项目数量不能超过100000000
列表模型ListModel:动态数据管理
ListModel是QML中最常用的模型之一,它允许在QML中直接定义结构化数据,并支持动态修改。
基本用法
import QtQuick 2.0
ListModel {
id: fruitModel
ListElement {
name: "Apple"
cost: 2.45
}
ListElement {
name: "Orange"
cost: 3.25
}
}
在视图中使用:
ListView {
anchors.fill: parent
model: fruitModel
delegate: Row {
Text { text: "名称:" + name }
Text { text: "价格:" + cost }
}
}
ListElement特性:
- 必须以小写字母开头
- 值只能是简单类型:字符串、布尔值、数字或枚举值
- 支持嵌套ListElement,创建层次结构
动态操作
ListModel提供了一系列方法用于动态修改数据:
// 添加数据
fruitModel.append({"name": "Banana", "cost": 1.95})
// 插入数据
fruitModel.insert(1, {"name": "Pear", "cost": 3.50})
// 修改数据
fruitModel.set(0, {"name": "Pineapple", "cost": 2.75})
// 移动数据
fruitModel.move(0, 2, 1) // 将第0项移动到第2项
// 删除数据
fruitModel.remove(1) // 删除第1项
// 清空模型
fruitModel.clear()
高级特性
动态角色:ListModel支持动态添加角色,只需设置dynamic属性为true:
ListModel {
id: dynamicModel
dynamic: true
// 可以后续添加新角色
}
Component.onCompleted: {
dynamicModel.append({"newRole": "value"})
}
与WorkerScript配合:对于大数据量操作,可以在后台线程处理模型以避免界面卡顿:
WorkerScript {
id: worker
source: "script.mjs"
}
Timer {
id: timer
interval: 2000; repeat: true
running: true
onTriggered: {
var msg = {'action': 'appendCurrentTime', 'model': listModel};
worker.sendMessage(msg);
}
}
ListModel非常适合中等规模的数据集,它提供了QML中直接操作数据的便利性,但对于大规模数据或复杂数据结构,建议使用C++实现的模型。
对象模型ObjectModel:封装可视化项
ObjectModel是一种特殊模型,它直接包含可视化项而不是数据,因此不需要委托(delegate)。
基本用法
import QtQuick 2.12
import QtQml.Models 2.12
ObjectModel {
id: itemModel
Rectangle { height: 30; width: 80; color: "red" }
Rectangle { height: 30; width: 80; color: "green" }
Rectangle { height: 30; width: 80; color: "blue" }
}
ListView {
anchors.fill: parent
model: itemModel
}
特点:
- 模型项本身就是可视化组件
- 不需要定义delegate
- 适合固定且数量有限的可视化项集合
- 性能优于Repeater生成的相同数量项
动态操作
ObjectModel也支持动态修改内容:
// 添加项
itemModel.append(rectComponent.createObject())
// 插入项
itemModel.insert(1, textComponent.createObject())
// 移动项
itemModel.move(0, itemModel.count-1, 1)
// 删除项
itemModel.remove(0)
// 获取项
var item = itemModel.get(2)
与C++集成
ObjectModel也可以从C++端创建和管理:
class ObjectModel : public QObject {
Q_OBJECT
public:
Q_INVOKABLE QVariant _objmodel() {
return QVariant::fromValue(objmodellist);
}
// 其他操作接口...
private:
QList<QObject*> objmodellist;
};
在QML中使用:
ListView {
delegate: Text { text: model.modelData.data }
model: Global._objmodel()
}
ObjectModel非常适合混合布局场景,其中某些项需要特殊定制而不是统一的数据驱动委托。
表格模型TableModel:处理二维数据
TableModel用于处理表格数据,相比ListModel,它增加了列的概念,适合展示类似数据库表格的结构化数据。
QML实现
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQml.Models 2.15
TableView {
anchors.fill: parent
columnSpacing: 1
rowSpacing: 1
model: TableModel {
TableModelColumn { display: "name" }
TableModelColumn { display: "age" }
rows: [
{
"name": "Alice",
"age": 22
},
{
"name": "Bob",
"age": 25
}
]
}
delegate: Rectangle {
Text { text: display }
}
}
C++实现
对于更复杂的表格,通常需要在C++中实现:
class DataModel : public QAbstractTableModel {
Q_OBJECT
public:
int rowCount(const QModelIndex&) const override {
return m_data.size();
}
int columnCount(const QModelIndex&) const override {
return m_roleList.size();
}
QVariant data(const QModelIndex &index, int role) const override {
return m_data[index.row()].at(index.column());
}
QHash<int, QByteArray> roleNames() const override {
QHash<int, QByteArray> roles;
for(int i=0; i<m_roleList.size(); i++) {
roles[Qt::UserRole+i+1] = m_roleList.at(i).toLocal8Bit();
}
return roles;
}
private:
QList<QVariantList> m_data;
QStringList m_roleList;
};
注册到QML:
qmlRegisterType<DataModel>("CustomModels", 1, 0, "DataModel");
高级特性
动态列管理:可以通过动态添加TableModelColumn来创建灵活的表结构
行选择:配合SelectionModel实现行选择功能
排序:通过实现sort函数添加排序能力
性能优化:对于大型表格,需要实现fetchMore/canFetchMore进行分批加载
TableModel特别适合展示业务数据,如数据库查询结果、电子表格等结构化信息。
XML列表模型XmlListModel:处理XML数据 使用较少
XmlListModel专门用于解析和展示XML格式的数据,常见于RSS阅读器或Web API交互场景。
基本用法
import QtQuick 2.0
import QtQuick.XmlListModel 2.0
XmlListModel {
id: xmlModel
source: "http://www.example.com/feed.xml"
query: "/rss/channel/item"
XmlRole { name: "title"; query: "title/string()" }
XmlRole { name: "pubDate"; query: "pubDate/string()" }
}
ListView {
width: 180; height: 300
model: xmlModel
delegate: Text { text: title + ": " + pubDate }
}
关键属性:
- source:XML数据源,可以是本地文件或网络URL
- query:XPath表达式,指定要处理的节点集
- XmlRole:定义如何从XML节点提取数据
- namespaceDeclarations:处理含命名空间的XML
动态查询
XmlListModel支持动态修改查询条件:
// 只获取特定条件的项目
xmlModel.query = "/rss/channel/item[contains(title, 'Qt')]"
// 重新加载模型
xmlModel.reload()
错误处理
通过status属性处理加载状态:
Text {
text: {
switch(xmlModel.status) {
case XmlListModel.Loading: return "加载中...";
case XmlListModel.Ready: return "就绪";
case XmlListModel.Error: return "错误: "+xmlModel.errorString();
}
}
}
性能考虑
- 大型XML文件考虑在后台线程解析
- 使用progress属性显示加载进度
- 合理设置XPath查询,避免复杂查询影响性能
XmlListModel极大简化了XML数据处理流程,是构建RSS阅读器或与Web服务交互的理想选择。
Package机制:多视图共享委托--使用较少
Package(包)机制允许多个视图共享同一组委托实例,实现复杂的视图交互效果。
基本概念
Package与DelegateModel配合使用,可以:
- 在不同视图中复用同一委托
- 实现项目在不同视图间动画过渡
- 构建主从视图(Master-Detail)布局
实现示例
import QtQuick 2.12
import QtQml.Models 2.12
import Qt.labs.qmlmodels 1.0
DelegateModel {
id: visualModel
model: ListModel {
ListElement { name: "Item 1"; color: "red" }
ListElement { name: "Item 2"; color: "green" }
}
delegate: Package {
Item { Package.name: "list" }
Item { Package.name: "grid" }
Rectangle {
width: parent.width; height: 50
color: model.color
Text { text: model.name }
parent: viewSwitcher.checked ? grid : list
}
}
}
ListView {
width: 200; height: 300
model: visualModel.parts.list
}
GridView {
x: 210; width: 300; height: 300
model: visualModel.parts.grid
}
Package机制为构建复杂视图交互提供了强大支持,是QML模型系统中较高级但极具价值的功能。
模型性能优化与实践建议
在实际项目中使用QML模型时,性能和可维护性是需要重点考虑的因素。
性能优化技巧
虚拟化:ListView/GridView等视图默认只创建可见项委托,确保clip属性为true
轻量委托:保持委托尽可能简单,复杂委托会显著影响滚动性能
批处理操作:对于大规模数据修改,先调用beginResetModel(),修改完成后调用endResetModel()
C++模型:数据量超过1000项时考虑使用C++实现的模型
缓存策略:对于网络数据或计算成本高的模型,实现缓存机制
模型选择指南
模型类型 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
整数模型 | 简单重复项 | 极简、高效 | 无数据绑定 |
ListModel | 中小型动态数据 | QML内定义、易用 | 大数据性能差 |
ObjectModel | 固定可视化项 | 无需委托、灵活 | 不适合数据驱动 |
TableModel | 表格数据 | 行列结构、功能丰富 | 复杂度高 |
XmlListModel | XML数据 | 内置解析、XPath支持 | 仅只读 |
C++模型 | 大型专业应用 | 高性能、功能全面 | 需要C++知识 |
常见问题解决
数据不同步:确保模型变更时发出正确的信号(如dataChanged)
内存泄漏:ObjectModel中注意管理QObject生命周期
跨线程访问:使用WorkerScript处理耗时操作
复杂过滤:考虑使用SortFilterProxyModel等中间模型
最佳实践
角色命名:使用有意义的角色名而非简单role1、role2
模块化:将复杂模型分离到单独QML文件或C++类中
测试:大数据量下测试滚动性能和内存占用
文档:为自定义模型编写使用说明,特别是角色定义
渐进增强:简单需求先用ListModel,复杂时再迁移到C++模型
结语
QML的模型系统提供了从简单到复杂、从纯QML到C++集成的全方位解决方案。掌握这些模型类型的特点和使用场景,能够帮助开发者构建数据驱动的高性能用户界面。无论是简单的整数模型还是复杂的C++集成模型,Qt都提供了相应的工具和模式,关键在于根据项目需求选择合适的工具。
随着Qt的持续发展,QML的模型系统也在不断进化,如最新的Qt 6中引入的DelegateChooser等新特性,进一步增强了模型的灵活性。建议开发者定期查阅Qt官方文档,了解最新的最佳实践和功能增强。