用 C++ 与 QML 对象交互

所有 QML 对象类型都是QObject-derived 类型,无论它们是由引擎内部实现还是由第三方定义。这意味着 QML 引擎可以使用 Qt Qml元对象系统(QtMeta Object System)动态实例化任何 QML 对象类型,并检查创建的对象。

这对于从 C++ 代码中创建 QML 对象非常有用,无论是显示可视化的 QML 对象,还是将非可视化的 QML 对象数据集成到 C++ 应用程序中。创建 QML 对象后,可从 C++ 检查该对象,以便读写属性、调用方法和接收信号通知。

有关 C++ 和不同 QML 集成方法的更多信息,请参阅C++ 和 QML 集成概述页面。

从 C++ 加载 QML 对象

QML 文档可通过QQmlComponentQQuickView 加载。QQmlComponent 将 QML 文档加载为 C++ 对象,然后可通过 C++ 代码修改。QQuickView 也可这样做,但由于QQuickViewQWindow 衍生的类,加载的对象也将呈现为可视化显示;QQuickView 通常用于将可显示的 QML 对象集成到应用程序的用户界面中。

例如,假设有一个MyItem.qml 文件,它看起来像这样:

import QtQuick

Item {
    width: 100; height: 100
}

这个 QML 文档可以用QQmlComponentQQuickView 加载,C++ 代码如下。使用QQmlComponent 需要调用QQmlComponent::create() 来创建一个新的组件实例,而QQuickView 会自动创建一个组件实例,可通过QQuickView::rootObject() 访问:

// Using QQmlComponent
QQmlEngine engine;
QQmlComponent component(&engine,
        QUrl::fromLocalFile("MyItem.qml"));
QObject *object = component.create();
...
delete object;
// Using QQuickView
QQuickView view;
view.setSource(QUrl::fromLocalFile("MyItem.qml"));
view.show();
QObject *object = view.rootObject();

object 就是已创建的MyItem.qml 组件的实例。现在,您可以使用QObject::setProperty() 或QQmlProperty::write() 修改该项目的属性:

object->setProperty("width", 500);
QQmlProperty(object, "width").write(500);

QObject::setProperty()QQmlProperty::write() 的区别在于,后者除了设置属性值外,还会删除绑定。例如,假设上面的width 赋值是与height 的绑定:

width: height

如果Itemheightobject->setProperty("width", 500) 调用后发生变化,width 将再次更新,因为绑定仍然有效。但是,如果heightQQmlProperty(object, "width").write(500) 调用后发生变化,width 将不会改变,因为绑定已不存在。

另外,也可以将对象转换为实际类型,并调用具有编译时安全性的方法。在这种情况下,MyItem.qml 的基对象是由QQuickItem 类定义的Item

QQuickItem *item = qobject_cast<QQuickItem*>(object);
item->setWidth(500);

您还可以使用QMetaObject::invokeMethod() 和QObject::connect() 连接到任何信号或调用组件中定义的方法。详见下面的调用 QML 方法连接 QML 信号

通过定义明确的 C++ 接口访问 QML 对象

从 C++ 与 QML 交互的最佳方法是在 C++ 中定义一个接口,并在 QML 本身中访问它。使用其他方法,重构 QML 代码很容易导致 QML / C++ 交互中断。对 QML 和 C++ 代码的交互进行推理也有帮助,因为通过 QML 驱动,用户和工具(如 qmllint)都能更容易地对其进行推理。从 C++ 访问 QML 会导致 QML 代码无法理解,除非手动验证没有外部 C++ 代码在修改给定的 QML 组件,即使这样,访问的范围也可能会随着时间的推移而改变,因此继续使用这种策略会增加维护负担。

要让 QML 驱动交互,首先需要定义一个 C++ 接口:

class CppInterface : public QObject
{
    Q_OBJECT
    QML_ELEMENT
    // ...
};

使用 QML 驱动的方法,可以通过两种方式与该接口交互:

单子

一种方法是在接口中添加QML_SINGLETON 宏,将接口注册为单例,让所有组件都能使用它。然后,通过简单的导入语句就可以使用该接口:

import my.company.module

Item {
    Component.onCompleted: {
        CppInterface.foo();
    }
}

如果您需要在根组件之外的更多地方使用接口,请使用这种方法,因为如果只是简单地传递一个对象,则需要通过属性将其明确传递给其他组件,或者使用缓慢且不推荐的方法,即使用非限定访问

初始属性

另一种方法是通过QML_UNCREATABLE 将接口标记为不可创建,并通过使用QQmlComponent::createWithInitialProperties() 和 QML 端的required 属性将其提供给根 QML 组件。

您的根组件可能是这样的

import QtQuick

Item {
    required property CppInterface interface
    Component.onCompleted: {
        interface.foo();
    }
}

在此将属性标记为必填,可防止在未设置接口属性的情况下创建组件。

然后,你可以按照从 C++ 加载 QML 对象(Loading QML Objects from C++except usingcreateWithInitialProperties() )中概述的方法初始化你的组件:

component.createWithInitialProperties(QVariantMap{{u"interface"_s, QVariant::fromValue<CppInterface *>(new CppInterface)}});

如果你知道你的接口只需要提供给根组件,那么这种方法是首选。它还允许在 C++ 端更容易地连接到接口的信号和槽。

如果这两种方法都不适合你的需要,你可能需要研究一下C++ 模型的用法。

通过对象名访问加载的 QML 对象

QML 组件本质上是对象树,它的子对象有同级和自己的子对象。QML 组件的子对象可以使用QObject::objectName 属性QObject::findChild() 来定位。例如,如果MyItem.qml 中的根项有一个子Rectangle 项:

import QtQuick

Item {
    width: 100; height: 100

    Rectangle {
        anchors.fill: parent
        objectName: "rect"
    }
}

子对象可以这样定位

QObject *rect = object->findChild<QObject*>("rect");
if (rect)
    rect->setProperty("color", "red");

请注意,一个对象可能有多个具有相同objectName 的子对象。例如,ListView 会创建其委托的多个实例,因此,如果其委托是以特定 objectName 声明的,则ListView 会有多个具有相同objectName 的子代。在这种情况下,可以使用QObject::findChildren() 查找所有与objectName 匹配的子代。

警告: 虽然可以从 C++ 访问 QML 对象并对其进行操作,但除了测试和原型设计目的外,不推荐使用这种方法。QML 和 C++ 整合的优势之一是能在 QML 中实现与 C++ 逻辑和数据集后端分离的用户界面,如果 C++ 端开始直接操作 QML,这一点就会失效。如果 C++ 方面开始直接操作 QML,这一点就会失效。这种方法也会使更改 QML 用户界面而不影响其 C++ 对应部分变得困难。

从 C++ 访问 QML 对象类型的成员

属性

QML 对象中声明的任何属性都可自动从 C++ 访问。给定一个如下的 QML 项目

// MyItem.qml
import QtQuick

Item {
    property int someNumber: 100
}

someNumber 属性的值可通过QQmlPropertyQObject::setProperty() 和QObject::property() 设置和读取:

QQmlEngine引擎;QQmlComponentcomponent(&engine, "MyItem.qml");QObject*object =component.create();
qDebug() << "Property value:" << QQmlProperty::read(object, "someNumber").toInt();
QQmlProperty::write(object, "someNumber", 5000);
qDebug() << "Property value:" << object->property("someNumber").toInt();
object->setProperty("someNumber", 100);

你应该总是使用QObject::setProperty(),QQmlPropertyQMetaProperty::write() 来改变 QML 属性值,以确保 QML 引擎知道属性的改变。例如,你有一个自定义类型PushButton ,它的buttonText 属性在内部反映了m_buttonText 成员变量的值。像这样直接修改成员变量不是个好主意:

//bad code
QQmlComponent component(engine, "MyButton.qml");
PushButton *button = qobject_cast<PushButton*>(component.create());
button->m_buttonText = "Click me";

因为值是直接改变的,这绕过了 Qt 的元对象系统,QML 引擎不会意识到属性的改变。这意味着与buttonText 的属性绑定不会更新,也不会调用任何onButtonTextChanged 处理程序。

调用 QML 方法

所有 QML 方法都暴露在元对象系统中,可使用QMetaObject::invokeMethod() 从 C++ 调用。您可以在冒号后指定参数和返回值的类型,如下面的代码片段所示。例如,当你想把 C++ 中具有特定签名的信号连接到 QML 定义的方法时,这就很有用。如果省略类型,C++ 签名将使用QVariant

下面是一个使用QMetaObject::invokeMethod() 调用 QML 方法的 C++ 应用程序:

QML
// MyItem.qml
import QtQuick

Item {
    function myQmlFunction(msg: string) : string {
        console.log("Got message:", msg)
        return "some return value"
    }
}
C++
// main.cppQQmlEngine引擎QQmlComponentcomponent(&engine, "MyItem.qml");QObject*object =component.create();QString返回值;QStringmsg= "Hello from C++"QMetaObject::invokeMethod(object, "myQmlFunction",Q_RETURN_ARG(QString,returnedValue),Q_ARG(QStringmsg));
qDebug() << "QML function returned:" << returnedValue;
删除对象;

注意冒号后指定的参数和返回类型。您可以使用值类型对象类型作为类型名。

如果在 QML 中省略了类型或指定为var ,那么在调用QMetaObject::invokeMethod 时,必须将QVariant 作为类型与Q_RETURN_ARG() 和Q_ARG() 一起传递。

连接 QML 信号

所有 QML 信号都自动提供给 C++,并可像任何普通 Qt Q++ 信号一样使用QObject::connect() 连接。反过来,QML 对象可使用信号处理器接收任何 C++ 信号。

下面是一个 QML 组件,它有一个名为qmlSignal 的信号,发射时带有一个字符串类型的参数。该信号通过QObject::connect() 连接到 C++ 对象的槽,因此每当qmlSignal 发送时,就会调用cppSlot() 方法:

// MyItem.qml
import QtQuick

Item {
    id: item
    width: 100; height: 100

    signal qmlSignal(msg: string)

    MouseArea {
        anchors.fill: parent
        onClicked: item.qmlSignal("Hello from QML")
    }
}
classMyClass :publicQObject
{ Q_OBJECTpublic slots:voidcppSlot(constQString&msg) {        qDebug() << "Called the C++ slot with message:" << msg;
    };intmain(intargc, char *argv[]) { QGuiApplicationapp(argc,argv);    QQuickViewview(QUrl::fromLocalFile("MyItem.qml"));    QObject*item =view.rootObject(); MyClass myClass    QObject::connect(item,SIGNAL(qmlSignal(QString)), &myClass,SLOT(cppSlot(QString))); view.show();returnapp.exec(); }

信号参数中的 QML 对象类型在 C++ 中被转换为指向类的指针:

// MyItem.qml
import QtQuick 2.0

Item {
    id: item
    width: 100; height: 100

    signal qmlSignal(anObject: Item)

    MouseArea {
        anchors.fill: parent
        onClicked: item.qmlSignal(item)
    }
}
classMyClass :publicQObject
{ Q_OBJECT公共 插槽voidcppSlot(QQuickItem*item) {       qDebug() << "Called the C++ slot with item:" << item;

       qDebug() << "Item dimensions:" << item->width()
               <<  item->height(); } };intmain(intargc, char *argv[]) { QGuiApplicationapp(argc,argv);    QQuickViewview(QUrl::fromLocalFile("MyItem.qml"));    QObject*item =view.rootObject(); MyClass myClass    QObject::connect(item,SIGNAL(qmlSignal(QVariant)), &myClass,SLOT(cppSlot(QVariant))); view.show();returnapp.exec(); }

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.