QML 中关联 C++ Model 类的两种核心方式:import 和 setContextProperty。
这两种方式的本质区别在于 Model 的提供者和作用域。
方式一:注册为 QML 类型并 Import 使用
这种方式是将 C++ 类注册到 QML 类型系统中,使其在 QML 中像一个内置类型一样被使用。
实现步骤:
- 在 C++ 中注册类型:使用 qmlRegisterType 函数将你的 Model 类注册为一个可在 QML 中实例化的类型。
- 在 QML 中 Import:在使用该 Model 的 QML 文件中,通过 import 语句导入其所在的模块。
- 在 QML 中声明实例化:像使用 Rectangle、Text 等元素一样,直接使用注册的类名来创建对象。
代码示例:
C++ 部分 (main.cpp 或某个初始化文件)
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> // 假设你的 Model 类头文件 #include "mystringlistmodel.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; // 关键步骤:将 MyStringListModel 注册为 QML 类型 // 参数:<模块名(如MyApp)>、<主版本号>、<次版本号>、<QML中的类型名> qmlRegisterType<MyStringListModel>("MyModels", 1, 0, "MyStringListModel"); engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); return app.exec(); }C++ Model 类 (mystringlistmodel.h/cpp)
这里以一个简单的字符串列表模型为例。
// mystringlistmodel.h #ifndef MYSTRINGLISTMODEL_H #define MYSTRINGLISTMODEL_H #include <QAbstractListModel> #include <QStringList> class MyStringListModel : public QAbstractListModel { Q_OBJECT public: explicit MyStringListModel(QObject *parent = nullptr); // 必须重写的函数 int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; // 可选:如果需要可编辑,重写此函数 bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex &index) const override; // 自定义角色 enum Roles { NameRole = Qt::UserRole + 1, ColorRole }; Q_ENUM(Roles) // 将枚举暴露给 QML // 添加数据的方法 public slots: void addItem(const QString &name, const QString &color); private: QStringList m_names; QStringList m_colors; }; #endif // MYSTRINGLISTMODEL_H(.cpp 文件实现略,重点是展示如何在 QML 中使用)
QML 部分 (main.qml)
import QtQuick 2.15 import QtQuick.Controls 2.15 import MyModels 1.0 // 导入我们注册的模块 ApplicationWindow { width: 400 height: 300 visible: true // 直接在 QML 中声明并实例化我们的 Model // 就像使用一个普通的 QML Item 一样 MyStringListModel { id: stringModel // 可以在这里直接调用其槽函数来初始化数据 Component.onCompleted: { addItem("Apple", "red") addItem("Banana", "yellow") addItem("Grape", "purple") } } ListView { anchors.fill: parent model: stringModel // 将 ListView 的 model 设置为这个实例 delegate: Rectangle { width: ListView.view.width height: 40 color: model.color // 使用我们在 C++ 中定义的 ColorRole Text { text: model.name // 使用 NameRole anchors.centerIn: parent } } } }特点与适用场景:
• 特点:
◦ 模块化:Model 成为 QML 环境的一部分,可以被任何导入相应模块的 QML 文件使用。 ◦ 封装性好:Model 的创建和配置逻辑可以完全放在 QML 端。 ◦ 可复用性高:同一个 Model 类可以在多个地方被实例化。 ◦ 类型安全:在编译时就能检查类型错误(如果工具支持)。• 适用场景:
◦ 通用的、可复用的 Model 组件。 ◦ 希望将 Model 的创建和生命周期管理完全交给 QML 的场景。 ◦ 构建大型的、模块化的 QML 应用程序。方式二:使用 setContextProperty
这种方式是在 C++ 端将一个已经创建好的 QObject 派生类(包括 Model)实例设置为 QML 引擎的上下文属性,使其在整个 QML 上下文中可用。
实现步骤:
- 在 C++ 中创建实例:在 C++ 代码中(通常是 main 函数中),创建你的 Model 类的实例。
- 设置上下文属性:使用 QQmlEngine::rootContext()->setContextProperty() 将这个实例注册为一个全局可用的 QML 对象。
- 在 QML 中直接使用:在 QML 中,可以直接使用你设置的名称来访问这个对象,无需 import。
代码示例:
C++ 部分 (main.cpp) #include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include "mystringlistmodel.h" // 同样的 Model 类 int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; // 1. 在 C++ 端创建 Model 实例 MyStringListModel stringModel; // 可以添加一些初始数据 // stringModel.addItem(...); // 2. 关键步骤:将实例设置为上下文属性 // 参数:<在QML中使用的名称>, <C++对象的指针> engine.rootContext()->setContextProperty("myCppModel", &stringModel); engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); return app.exec(); }QML 部分 (main.qml)
import QtQuick 2.15 import QtQuick.Controls 2.15 // 注意:这里没有 ‘import MyModels 1.0’ ApplicationWindow { width: 400 height: 300 visible: true // 注意:没有在 QML 中声明 MyStringListModel // 它已经在 C++ 中被创建好了 ListView { anchors.fill: parent // 3. 直接使用在 C++ 中设置的上下文属性名 ‘myCppModel’ model: myCppModel delegate: Rectangle { width: ListView.view.width height: 40 color: model.color Text { text: model.name anchors.centerIn: parent } } } // 甚至可以在 Button 的 onClicked 中直接调用 C++ Model 的方法 Button { text: "Add Item" anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter onClicked: { // 直接调用 C++ 对象的槽函数 myCppModel.addItem("New Fruit", "green") } } }特点与适用场景:
• 特点:
◦ 简单直接:对于单个实例或单例模式的 Model 非常方便。 ◦ 控制权在 C++:Model 的生命周期由 C++ 控制,通常在整个应用运行期间都存在。 ◦ 全局可用:一旦设置,在所有导入了该上下文的 QML 文件中都可以访问。 ◦ 无需注册类型:不需要使用 qmlRegisterType,代码稍简洁一些。• 适用场景:
◦ 应用程序的全局状态管理(如用户数据、配置信息)。 ◦ 单例服务或管理器类。 ◦ 需要与 C++ 深度交互、且只有一个实例的 Model。 ◦ 快速原型开发或小型项目。对比总结
特性 注册类型 (qmlRegisterType) 设置上下文属性 (setContextProperty)
提供者 QML 引擎的类型系统 QML 引擎的根上下文
使用方式 import + 声明式实例化 直接使用属性名
生命周期 QML 垃圾回收机制管理 C++ 端管理(通常是长期存在)
作用域 模块内可见 整个 QML 上下文可见(全局)
复用性 高,可创建多个实例 低,通常只有一个实例
灵活性 高,可在 QML 中任意创建和配置 较低,实例化和配置主要在 C++
典型用例 可复用的 UI 组件、通用 Model 全局状态、单例服务、管理器
如何选择?
• 如果你的 Model 是数据驱动的、需要被多个视图复用、或者其行为和创建逻辑更适合在 QML 中描述,请选择 注册类型。
• 如果你的 Model 代表一个全局的、单一的应用程序状态或服务,并且其生命周期应该由 C++ 牢牢掌控,请选择 设置上下文属性。
在实际项目中,这两种方式常常结合使用。例如,使用上下文属性提供一个全局的配置 Model,同时使用注册类型为不同的列表页面提供特定的数据 Model。