本教程将教你如何在 Qt Quick 项目中使用 C++ 扩展 QML 类型,具体包括定义可被 QML 调用的类、配置支持混合开发的 CMake 项目,并演示如何在 QML 中使用这些类型,以一个包含 DemoController
类的示例项目为基础逐步讲解。
项目结构概览
在开始之前,先了解一下项目的文件结构。这些文件共同构成了一个完整的 Qt Quick 应用程序:
- CMakeLists.txt:项目的构建配置文件,用于管理 C++ 和 QML 的编译。
- main.cpp:程序的入口文件,负责启动应用程序并加载 QML 文件。
- demo_controller.h 和 demo_controller.cpp:定义并实现了一个 C++ 类
DemoController
,它将被 QML 调用。 - qml.qrc:资源文件,用于将 QML 文件嵌入到程序中。
- main.qml:QML 文件,定义了用户界面并使用了 C++ 扩展的类型。
使用 C++ 扩展 QML 类型
定义 C++ 类
要让一个 C++ 类能在 QML 中使用,必须满足以下要求:
- 继承自
QObject
,这是 Qt 元对象系统的核心类。 - 使用
Q_OBJECT
宏,以便支持信号、槽和属性。 - 使用
Q_PROPERTY
宏定义可以在 QML 中访问的属性(可选)。 - 使用
Q_INVOKABLE
宏或slots
关键字定义可以在 QML 中调用的方法。 - 使用
QML_ELEMENT
宏将类注册为 QML 类型。
以下是 demo_controller.h
中的代码:
#pragma once#include <QObject>
#include <QQmlEngine>class DemoController : public QObject
{Q_OBJECTQ_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged REQUIRED)Q_PROPERTY(QString url MEMBER m_url NOTIFY urlChanged)QML_ELEMENT // 注册为 QML 类型public:explicit DemoController(QObject *parent = nullptr);QString name() const;void setName(const QString &name);public slots:Q_INVOKABLE void performAction();Q_INVOKABLE QString fetchData(int index, const std::vector<int> &dataArray,const QVariantMap &dataMap);signals:void nameChanged();void urlChanged();void actionTriggered();private:QString m_url;
};
代码解释:
Q_OBJECT
:启用 Qt 的元对象功能,必须放在类的私有部分顶部。Q_PROPERTY
:name
:定义了一个属性,具有读取函数 (READ name
)、写入函数 (WRITE setName
) 和变更信号 (NOTIFY nameChanged
)。url
:定义了一个属性,直接绑定到成员变量m_url
,并在值改变时发出urlChanged
信号。
QML_ELEMENT
:将类注册为 QML 类型,之后可以在 QML 中直接实例化。- 方法:
performAction()
:一个简单的函数,标记为Q_INVOKABLE
,可在 QML 中调用。fetchData()
:一个带参数的函数,处理数据并返回字符串。
- 信号:
nameChanged
、urlChanged
和actionTriggered
是可以被 QML 监听的事件。
实现 C++ 类
接下来,在 demo_controller.cpp
中实现这些声明的功能:
#include "demo_controller.h"
#include <QDebug>DemoController::DemoController(QObject *parent) : QObject(parent)
{qDebug() << "控制器实例已创建";
}QString DemoController::name() const
{return objectName();
}void DemoController::setName(const QString &name)
{if (name == objectName()) return;setObjectName(name);emit nameChanged();
}void DemoController::performAction()
{qDebug() << "执行基础操作";emit actionTriggered();
}QString DemoController::fetchData(int index, const std::vector<int> &dataArray,const QVariantMap &dataMap)
{qDebug() << "获取数据,索引:" << index;qDebug() << "数组数据:";for (const auto &value : dataArray) {qDebug() << value;}qDebug() << "映射数据:";for (auto it = dataMap.begin(); it != dataMap.end(); ++it) {qDebug() << it.key() << ":" << it.value().toString();}return "数据处理完成";
}
代码解释:
- 构造函数:初始化时输出一条调试信息。
name()
和setName()
:分别获取和设置name
属性,使用objectName()
存储值,改变时发出信号。performAction()
:输出调试信息并触发actionTriggered
信号。fetchData()
:接收一个整数索引、一个整数向量和一个键值映射,打印这些数据并返回结果。
配置项目(CMakeLists.txt)
为了让 C++ 和 QML 协同工作,我们需要正确配置构建系统。这里使用的是 CMake:
cmake_minimum_required(VERSION 3.20)project(cpp_qml_module)set(CMAKE_PREFIX_PATH "C:/Qt/6.8.2/mingw_64")
find_package(Qt6 COMPONENTS Quick REQUIRED)set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)add_executable(${PROJECT_NAME}main.cppqml.qrcdemo_controller.hdemo_controller.cpp
)qt_add_qml_module(${PROJECT_NAME}URI Demo.Controller
)set(QML_IMPORT_PATH ${CMAKE_BINARY_DIR})target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Quick)
代码解释:
cmake_minimum_required
:指定最低 CMake 版本。project
:定义项目名称。set(CMAKE_PREFIX_PATH)
:设置 Qt 安装路径(需要根据你的环境调整)。find_package
:查找 Qt6 的 Quick 模块。set(CMAKE_AUTOMOC ON)
和set(CMAKE_AUTORCC ON)
:自动处理 Qt 的元对象编译器(MOC)和资源文件。add_executable
:添加可执行目标,列出所有源文件。qt_add_qml_module
:注册 QML 模块,指定 URI 为Demo.Controller
。set(QML_IMPORT_PATH)
:设置 QML 模块的导入路径。target_link_libraries
:链接 Qt6 的 Quick 库。
在 QML 中使用 C++ 类型
现在,我们在 main.qml
中使用刚刚定义的 DemoController
:
import QtQuick
import QtQuick.Controls
import Demo.ControllerWindow {width: 800height: 600visible: truetitle: "C++/QML 集成演示"Column {spacing: 10padding: 20width: parent.widthDemoController {id: mainControllername: "初始名称"onNameChanged: console.log("名称变更:", name)onUrlChanged: console.log("URL 变更:", url)onActionTriggered: console.log("操作已触发")}Button {text: "显示当前名称"onClicked: console.log("当前名称:", mainController.name)}Button {text: "修改名称"property int clickCount: 0onClicked: mainController.name = "新名称-" + (++clickCount)}Button {text: "修改 URL"onClicked: mainController.url = "https://example.com/" + Date.now()}Button {text: "执行操作"onClicked: mainController.performAction()}Button {text: "获取数据"onClicked: {const result = mainController.fetchData(100,[11, 22, 33],{"name": "测试", "value": 123})console.log("操作结果:", result)}}}
}
代码解释:
import Demo.Controller
:导入 C++ 注册的模块。DemoController
:实例化一个控制器对象,设置初始name
并监听信号。- 按钮功能:
- 显示当前
name
属性。 - 修改
name
属性并递增计数器。 - 修改
url
属性。 - 调用
performAction()
方法。 - 调用
fetchData()
方法,传入参数并打印结果。
- 显示当前
设置应用程序入口(main.cpp)
main.cpp
是程序的启动文件,负责加载 QML 文件:
#include <QGuiApplication>
#include <QQmlEngine>
#include <QQmlComponent>
#include <QQuickWindow>
#include <memory>int main(int argc, char *argv[])
{QGuiApplication app(argc, argv);QQmlEngine engine;QQmlComponent component(&engine);component.loadUrl(QUrl("qrc:/main.qml"));if (component.isError()) {qCritical() << "QML 加载错误:";for (const auto &error : component.errors()) {qCritical() << error.toString();}return EXIT_FAILURE;}std::unique_ptr<QQuickWindow> window(qobject_cast<QQuickWindow*>(component.create()));if (!window) {qCritical() << "窗口创建失败";return EXIT_FAILURE;}return app.exec();
}
代码解释:
- 初始化应用程序和 QML 引擎。
- 从资源文件加载
main.qml
。 - 检查加载是否出错,若出错则打印错误信息并退出。
- 创建窗口并运行事件循环。
配置资源文件(qml.qrc)
qml.qrc
文件将 QML 文件嵌入到程序中:
<RCC><qresource prefix="/"><file>main.qml</file></qresource>
</RCC>
代码解释:
- 将
main.qml
添加到资源系统中,可通过qrc:/main.qml
访问。
运行项目
步骤
- 安装环境:确保已安装 Qt6 和 CMake。
- 调整路径:在
CMakeLists.txt
中将CMAKE_PREFIX_PATH
改为你的 Qt 安装路径。 - 构建项目:
- 运行
cmake -B build
生成构建系统。 - 运行
cmake --build build
编译项目。
- 运行
- 运行程序:执行生成的可执行文件。
运行结果
你将看到一个窗口,包含五个按钮。点击按钮会触发与 DemoController
的交互,结果会输出到控制台。例如:
- 点击“显示当前名称”会打印当前的
name
值。 - 点击“获取数据”会调用
fetchData()
并显示处理结果。
总结
通过本教程,你已经掌握了以下内容:
- 如何定义 C++ 类:通过继承
QObject
并使用 Qt 宏,使其可被 QML 调用。 - 如何配置项目:使用 CMake 将 C++ 和 QML 集成。
- 如何在 QML 中使用:导入模块,访问属性、调用方法和监听信号。
C++ 和 QML 的结合充分利用了 C++ 的性能优势和 QML 的界面设计灵活性,是开发复杂 Qt Quick 应用程序的理想方式。希望你能通过这个示例项目,逐步熟悉这种混合开发模式,并在未来的学习中不断实践和提升!