本页内容

从 C++ 与 QML 对象交互

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

这对于从 C++ 代码创建 QML 对象非常有用,无论是为了显示可视觉渲染的 QML 对象,还是为了将非视觉的 QML 对象数据集成到 C++ 应用程序中。一旦创建了 QML 对象,就可以从 C++ 对其进行访问,从而读取和写入属性、调用方法以及接收信号通知。

有关 C++ 以及各种 QML 集成方法的更多信息,请参阅C++ 与 QML 集成概述页面。

从 C++ 加载 QML 对象

可以通过QQmlComponent 或QQuickView 加载QML文档。QQmlComponent 将QML文档加载为C++对象,随后可通过C++代码对其进行修改。QQuickView 同样具有此功能,但由于QQuickView 是QWindow 的派生类,因此加载的对象还会渲染为可视化界面;QQuickView 通常用于将可显示的QML对象集成到应用程序的用户界面中。

例如,假设有一个名为MyItem.qml 的文件,内容如下:

import QtQuick

Item {
    width: 100; height: 100
}

可以通过以下 C++ 代码使用QQmlComponent 或QQuickView 加载此 QML 文档。使用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

如果在调用object->setProperty("width", 500) 之后,Item 中的height 发生了变化,那么width 将会再次被更新,因为该绑定仍然有效。但是,如果在调用QQmlProperty(object, "width").write(500) 之后,height 发生了变化,那么width 将不会被更改,因为该绑定已不存在。

另外,您可以将对象强制转换为其实际类型,并在编译时安全地调用方法。在此情况下,MyItem.qml 的基类是Item ,该类由QQuickItem 类定义:

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 驱动交互也有助于理解 QML 与 C++ 代码之间的交互关系,因为这种方式既便于用户理解,也便于 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组件,同时在QML端设置一个必填属性。

您的根组件可能类似于以下形式:

import QtQuick

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

在此处将该属性标记为必填,可防止在未设置接口属性时创建该组件。

然后,您可以按照《从 C++ 加载 QML 对象》中所述的方式初始化组件,只是需要使用createWithInitialProperties() 代替:

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

如果您确定接口仅需供根组件使用,建议采用此方法。此外,该方法还能让 C++ 端更轻松地连接到接口的信号和槽。

如果这两种方法都不符合您的需求,您不妨研究一下C++ 模型的用法。

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

QML 组件本质上是由子对象组成的对象树,这些子对象既有同级对象,也有自己的子对象。可以通过使用 `QObject::objectName ` 属性并调用 `QObject::findChild()` 来定位 QML 组件的子对象。例如,如果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,这种优势将无法实现。此外,这种做法还会导致在修改 QML 用户界面时难以避免影响其对应的 C++ 代码。

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

属性

在 QML 对象中声明的任何属性,均可从 C++ 中自动访问。假设有一个如下所示的 QML 项:

// MyItem.qml
import QtQuick

Item {
    property int someNumber: 100
}

someNumber 属性的值可以通过QQmlProperty ,或者QObject::setProperty()和QObject::property()进行设置和读取:

QQmlEngine engine;
QQmlComponent component(&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()、QQmlProperty 或QMetaProperty::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.cpp
QQmlEngine engine;
QQmlComponent component(&engine, "MyItem.qml");
QObject*object =component.create();

QString 返回值;
QString msg= "Hello from C++";
QMetaObject::invokeMethod(object, "myQmlFunction",
        Q_RETURN_ARG(QString,返回值),
        Q_ARG(QString,msg));

qDebug() << "QML function returned:" << returnedValue;
deleteobject;

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

如果在 QML 中省略类型或将其指定为 `var `,则在调用 `QMetaObject::invokeMethod` 时,必须将 `QVariant ` 作为类型传递给 `Q_RETURN_ARG()` 和 `Q_ARG()`。

连接到 QML 信号

所有 QML 信号均可被 C++ 自动访问,并可像任何普通的 Qt C++ 信号一样,通过QObject::connect() 进行连接。反之,任何 C++ 信号均可通过信号处理程序被 QML 对象接收。

以下是一个 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_OBJECT
public slots:
    voidcppSlot(constQString&msg) {
        qDebug() << "Called the C++ slot with message:" << msg;
    }
};

intmain(intargc, char *argv[]) {
    QGuiApplication app(argc,argv);

    QQuickView view(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
public slots:
    voidcppSlot(QQuickItem*item) {
       qDebug() << "Called the C++ slot with item:" << item;

       qDebug() << "Item dimensions:" << item->width()
               << item->height();
    }
};

intmain(intargc, char *argv[]) {
    QGuiApplication app(argc,argv);

    QQuickView view(QUrl::fromLocalFile("MyItem.qml"));
    QObject*item =view.rootObject();

    MyClass myClass;
    QObject::connect(item,SIGNAL(qmlSignal(QVariant)),
                     &myClass,SLOT(cppSlot(QVariant)));

    view.show();
    returnapp.exec();
}

© 2026 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.