【QT Quick】C++扩展QML类型
本教程将教你如何在 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_OBJECT
Q_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.cpp
qml.qrc
demo_controller.h
demo_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.Controller
Window {
width: 800
height: 600
visible: true
title: "C++/QML 集成演示"
Column {
spacing: 10
padding: 20
width: parent.width
DemoController {
id: mainController
name: "初始名称"
onNameChanged: console.log("名称变更:", name)
onUrlChanged: console.log("URL 变更:", url)
onActionTriggered: console.log("操作已触发")
}
Button {
text: "显示当前名称"
onClicked: console.log("当前名称:", mainController.name)
}
Button {
text: "修改名称"
property int clickCount: 0
onClicked: 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 应用程序的理想方式。希望你能通过这个示例项目,逐步熟悉这种混合开发模式,并在未来的学习中不断实践和提升!