注册c++类/实例——>QML
本文介绍了QML与C++交互的核心方法,主要包括三个步骤:1)编写继承QObject的C++类,通过宏定义暴露属性、方法、信号和枚举;2)将C++类注册到QML引擎,可选择单例模式(setContextProperty)或可复用类型(qmlRegisterType);3)在QML中直接使用注册的C++对象或类型。文章详细讲解了嵌套子对象的处理方式,以及如何在QML中访问C++属性和方法。这种交互方
目录
注册为上下文属性 (setContextProperty):
前言
QML 作为一种灵活高效的界面开发语言已经越来越得到业界的认可。QML 负责界面,C++ 负责逻辑,这也是 Qt 官方推荐的开发方式。那么 QML 与 C++ 的交互必然是每一个Qt开发工程师需要掌握并且精通的。
Qt QML与C++后端通信的需求非常普遍,可以说是QML应用开发的核心和精髓。
在实际项目中,几乎所有的复杂应用都会采用这种架构。QML主要负责构建前端的用户界面(UI),而C++则负责处理后端业务逻辑,比如:
-
数据处理和管理: 数据库访问、文件读写、网络请求等。
-
计算密集型任务: 复杂的算法运算、图像处理等。
-
硬件交互: 与设备硬件(如串口、USB、传感器等)进行通信。

推荐一篇介绍的很详细且简单直白的文章(可以跟着这篇文章来进行QML与c++类交互实操):
Qt Quick QML 与 C++ 交互系列之一_qml的setcontextproperty-CSDN博客
整体过程
整个过程可以概括为:
- 设计C++类(继承QObject并使用宏) ->
- 在C++中注册到QML引擎(qmlRegisterType 或 setContextProperty) ->
- 在QML中无缝使用。
这套流程为C++和QML之间提供了一个强大的、类型安全的通信桥梁,让您可以利用C++的高性能来处理后端逻辑,同时使用QML的便捷性来构建用户界面。
1. 编写QML暴露的C++类
继承QObject
首先,你的C++类必须继承自QObject。这是使用Qt元对象系统的前提,它提供了信号与槽机制、属性系统以及其他元编程功能。同时,在类的定义中,必须使用Q_OBJECT宏。
Q_OBJECT宏告诉元对象编译器(MOC)为你的类生成额外的代码,以便支持元对象系统的功能。
暴露属性(Properties)
要将一个C++属性暴露给QML,需要使用Q_PROPERTY宏。这个宏允许你定义一个属性,并指定其在QML中可读、可写或可通知。
Q_PROPERTY(type name READ function WRITE function NOTIFY signal)
一个典型的
Q_PROPERTY声明需要至少指定以下部分:
type: 属性的数据类型。
name: 属性的名称。
READ: 用于读取属性值的成员函数(getter)。
WRITE: (可选) 用于写入属性值的成员函数(setter)。
NOTIFY: (可选) 当属性值发生变化时发出的信号。这是非常重要的,虽然NOTIFY是可选但强烈推荐写上,当属性值改变时发出这个信号,QML中绑定的UI元素会自动更新
示例: 假设你有一个名为name的字符串属性需要暴露。
class Person : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
public:
Person(QObject *parent = nullptr);
QString name() const;
void setName(const QString &name);
signals:
void nameChanged();
private:
QString m_name;
};
在这个例子中:
- Q_PROPERTY宏将name属性暴露给QML。
- READ函数name()用于获取属性值。
- WRITE函数setName(const QString &)用于设置属性值。
- NOTIFY信号nameChanged()在setName函数中被发出,通知QML该属性已更改。
在setName的实现中,你需要在值改变时发出nameChanged信号:
void Person::setName(const QString &name) { if (m_name == name) return; m_name = name; emit nameChanged(); }
暴露方法(Methods)
要将C++方法暴露给QML,需要使用Q_INVOKABLE宏或者将方法声明为public slots。
- Q_INVOKABLE宏: 这是最推荐的方式。它使你的方法可以在QML中被调用。
- public slots: 将方法声明为公共槽(slot)也可以使其被QML调用。不过,Q_INVOKABLE更清晰地表达了其意图,即为QML提供可调用的方法。
class MyClass : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE void doSomething();
Q_INVOKABLE int add(int a, int b);
};
暴露信号(Signals)
信号是C++与QML之间进行事件通信的关键。只要你的类继承了QObject并使用了Q_OBJECT宏,你就可以定义信号。QML可以直接连接到这些信号,并在信号发出时触发相应的JavaScript函数。
C++类中的信号向QML暴露的时候,无需额外的宏修饰来暴露,都是默认暴露的,只要这个类使用了 Q_OBJECT 宏,QML就可以直接识别并连接到这些信号,并在信号发出时触发相应的JavaScript函数。
class MyClass : public QObject
{
Q_OBJECT
signals:
void dataReady(const QString &data);
};
在C++代码中,你可以通过emit关键字来发出信号:
void MyClass::fetchData()
{
// 假设获取了数据
QString result = "Hello from C++!";
emit dataReady(result);
}
在QML中,你可以通过on<SignalName>语法来连接这个信号:
// 假设MyClass的实例名为myObject
MyClass {
id: myObject
onDataReady: {
console.log("Received data:", data);
}
}
暴露枚举(Enums)
要将C++枚举暴露给QML,你需要将枚举类型放在一个继承自QObject的类中,并使用Q_ENUM宏。
#include <QObject>
class DataManager : public QObject
{
Q_OBJECT
public:
explicit DataManager(QObject *parent = nullptr);
// 将枚举放在一个QObject子类中
enum Status {
Idle,
Loading,
Ready,
Error
};
Q_ENUM(Status) // 注册这个枚举
// 可以在属性或方法中使用这个枚举类型
Q_PROPERTY(Status currentStatus READ currentStatus NOTIFY currentStatusChanged)
Status currentStatus() const;
signals:
void currentStatusChanged();
private:
Status m_currentStatus;
};
QML代码:
Item {
id: root
DataManager {
id: manager
// 访问枚举值
onCurrentStatusChanged: {
if (manager.currentStatus === DataManager.Ready) {
console.log("Data is ready!");
}
}
}
}
暴露嵌套的子对象
1. 设计 C++ 子对象
首先,作为嵌套的子对象,它必须符合所有暴露给 QML 的基本要求:
- 必须继承自 QObject。
- 必须在类定义中包含 Q_OBJECT 宏。
- 使用 Q_PROPERTY、Q_INVOKABLE 或 Q_SIGNAL 宏来暴露它自己的属性、方法和信号。
例如,一个网络配置子对象的类:
// networksettings.h
#include <QObject>
#include <QString>
class NetworkSettings : public QObject {
Q_OBJECT
Q_PROPERTY(QString serverUrl READ serverUrl WRITE setServerUrl NOTIFY serverUrlChanged)
Q_PROPERTY(int port READ port WRITE setPort NOTIFY portChanged)
public:
explicit NetworkSettings(QObject* parent = nullptr);
QString serverUrl() const;
void setServerUrl(const QString& url);
int port() const;
void setPort(int p);
signals:
void serverUrlChanged();
void portChanged();
private:
QString m_serverUrl;
int m_port;
};
在对应的.cpp 实现文件中,你需要提供相应的 setter 方法。
2.设计 C++ 父对象
接下来,需要设计一个父类来包含和管理这个子对象。父类也必须遵循暴露给 QML 的基本原则。
关键在于:
- 将子对象作为父类的成员变量。
- 通过 Q_PROPERTY 将这个成员变量暴露给 QML。这个属性的类型就是子对象的类名,并且属性的 READ 函数返回一个指向该子对象的指针。
// appsettings.h
#include <QObject>
#include "networksettings.h"
class AppSettings : public QObject {
Q_OBJECT
// 暴露子对象,注意其类型是 NetworkSettings*
// CONSTANT 表示它在运行时不会改变
Q_PROPERTY(NetworkSettings* network READ network CONSTANT)
public:
explicit AppSettings(QObject* parent = nullptr);
~AppSettings();
NetworkSettings* network() const;
private:
NetworkSettings* m_network;
};
3. 将父对象暴露给 QML
这里选择在c++中创建对应的对象实例,然后将这个父类对象暴露给QML,当然如果你需要在QML中多次创建这个父类对象的话,你也可以选择将父类暴露给QML,然后在QML中可以创建多个对象。
// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "appsettings.h"
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
// 1. 创建父对象实例
AppSettings appSettings;
// 2. 将父对象实例暴露给 QML
engine.rootContext()->setContextProperty("appSettings", &appSettings);
const QUrl url(u"qrc:/myapp/main.qml"_qs);
engine.load(url);
return app.exec();
}
4. 在 QML 中访问嵌套子对象
现在,在 QML 中,你可以通过父对象的名字 appSettings 访问其暴露的属性 network,然后继续访问 network 的属性和方法,形成一个清晰的层级调用。
// main.qml
import QtQuick
import QtQuick.Controls
Window {
width: 640
height: 480
visible: true
Column {
spacing: 10
anchors.centerIn: parent
Label {
text: "Server URL: " + appSettings.network.serverUrl
}
Label {
text: "Port: " + appSettings.network.port
}
Button {
text: "Change URL"
onClicked: {
appSettings.network.setServerUrl("https://new.server.com");
}
}
}
}
通过这种方式,你可以将复杂的 C++ 功能模块化,并以一种自然、分层的方式在 QML 中进行访问和管理,极大地提高了代码的组织性和可维护性。
2. 注册C++类或实例—>QML
在这一步,尼需要让QML引擎知道您的C++类或对象存在。有两种主要方法:
- 注册为上下文属性 (
setContextProperty):如果您的C++类是一个单例对象,或者您只需要在QML中访问一个已经创建好的特定实例,那么这种方法更简单直接。
-
注册为QML类型 (
qmlRegisterType):如果您的C++类是一个通用的组件或数据模型,并且可能需要在QML中多次实例化,那么这种方法最合适。这使得您的C++类就像一个内置的QML类型一样,可以在QML文档中直接创建和使用。
注册为上下文属性 (setContextProperty):
使用QQmlContext::setContextProperty,这种方法是把一个具体的C++对象实例注册到QML的上下文中。这个对象在QML中以一个特定的名称(比如"manager")全局可访问,就像一个单例一样。
适用场景: 当你需要一个全局管理器或控制器时,这种方法非常适合。例如,一个BackendManager类型对象负责所有网络请求、数据处理和系统状态管理,你希望在任何QML文件中都能直接访问它。
特点:这个实例在 C++ 代码中创建,并且在 QML 整个生命周期中是唯一的。QML 无法使用这个方法来创建新的实例。这就像是你在 QML 中创建了一个全局的单例对象。
// main.cpp
QQmlApplicationEngine engine;
BackendManager manager; // 创建一个C++对象实例
engine.rootContext()->setContextProperty("backendManager", &manager);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
代码解释
创建 QML 引擎 (QQmlApplicationEngine)。
创建一个自定义的 C++BackendManager类型 后端对象 (manager)。
把这个 C++ 对象通过 setContextProperty 注册到 QML 上下文,起名 "backendManager"。
加载并运行 main.qml,此时 QML 就能直接调用 backendManager 的方法或属性。
backendManager 被注册到了根上下文里,根上下文是整个 QML 引擎的“全局作用域”上下文。
在这个上下文里注册的属性(比如这里的 backendManager),会被该引擎加载的所有 QML 文件 共享。所以不管是 main.qml 还是 Page.qml,都可以直接用 backendManager,因为它是挂在根上下文的。
// main.qml
Text {
// 直接访问 C++ 对象的属性
text: backendManager.status
// 调用 C++ 对象的方法
onClicked: backendManager.doSomething()
}
注册为QML类型 (qmlRegisterType):
使用qmlRegisterType 这种方法是把一个C++类注册到QML的类型系统中,让QML能够像创建内置类型(如Rectangle)一样,动态地创建这个类的实例。
适用场景: 当你需要创建可重用的组件、数据模型或服务时,这种方法是首选。
这里我们省略一下MyCppClass类的定义
// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "mycppclass.h" // 包含你的 C++ 类头文件
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
// 注册 C++ 类
// 参数依次为:
// 1. QML 模块的 URI (例如 "com.mycompany.controls")
// 2. 主版本号 (例如 1)
// 3. 次版本号 (例如 0)
// 4. QML 中使用的类型名称 (例如 "MyCppClass")
qmlRegisterType<MyCppClass>("com.mycompany.controls", 1, 0, "MyCppClass");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
在 QML 中使用 C++ 类
在 QML 文件中,你需要通过 import 语句导入你的模块,然后就可以像使用任何其他 QML 类型一样使用自定义的 C++ 类了。
// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import com.mycompany.controls 1.0 // 导入你注册的 QML 模块
Window {
width: 640
height: 480
visible: true
title: qsTr("C++ QML Example")
// 实例化 C++ 类
MyCppClass {
id: myObject
name: "World" // 直接设置属性值
}
Column {
anchors.centerIn: parent
spacing: 10
Text {
// 通过 myObject 访问 C++ 属性
text: "Hello from QML, " + myObject.name
font.pixelSize: 24
}
Button {
text: "Change Name"
onClicked: {
// 在 QML 中调用 C++ 方法
myObject.setName("Gemini");
}
}
Button {
text: "Say Hello"
onClicked: {
// 调用 C++ 方法
myObject.sayHello();
}
}
}
}
3. 在QML中访问和使用
一旦C++类或对象被注册,您就可以在QML中像操作其他QML元素一样,直接访问其属性、调用其方法和连接其信号。
- 访问属性:直接使用点号.运算符访问,例如 myObject.name。
- 调用方法:直接调用,例如 myObject.doSomething()。
- 连接信号:使用 on<SignalName> 的形式连接信号,例如 onNameChanged: { ... }。
更多推荐
所有评论(0)