向 QML 公开 C++ 类型的属性
用 C++ 代码定义的功能可轻松扩展 QML。由于 QML 引擎与Qt XML元对象系统紧密集成,任何由QObject 衍生类或Q_GADGET 类型适当公开的功能都可从 QML 代码中访问。这使得 C++ 数据和函数可直接从 QML 访问,通常只需很少或无需修改。
QML 引擎能通过元对象系统内省QObject 实例。这意味着任何 QML 代码都能访问QObject 派生类实例的以下成员:
- 属性
- 方法(只要它们是公共槽或标记为Q_INVOKABLE)
- 信号
(此外,如果用Q_ENUM 声明了枚举,枚举也是可用的。详见QML 和 C++ 之间的数据类型转换)。
一般来说,无论QObject 派生的类是否已在 QML类型系统中注册,QML 都可以访问它们。然而,如果一个类的使用方式需要引擎访问额外的类型信息--例如,如果类本身要用作方法参数或属性,或者如果它的一个枚举类型要以这种方式使用--那么这个类可能需要注册。建议注册 QML 中使用的所有类型,因为只有注册过的类型才能在编译时被分析。
Q_GADGET 类型需要注册,因为它们不是从已知的公共基础派生的,不能自动提供。没有注册,它们的属性和方法就无法访问。
通过使用DEPENDENCIES选项在qt_add_qml_module调用中添加依赖关系,你可以在自己的模块中使用来自不同模块的 C++ 类型。例如,你可能想依赖QtQuick ,这样你的 QML 公开的 C++ 类型就可以使用QColor 作为方法参数和返回值。QtQuick 公开QColor 作为值类型 颜色。这种依赖关系可在运行时自动推断,但不应依赖它。
还请注意,本文档中涉及的许多重要概念已在《用 C++ 编写 QML 扩展》(Writing QML Extensions with C++)教程中演示。
有关 C++ 和不同 QML 集成方法的更多信息,请参阅C++ 和 QML 集成概述页面。
数据类型处理和所有权
从 C++ 传输到 QML 的任何数据,无论是属性值、方法参数或返回值,还是信号参数值,都必须是 QML 引擎支持的类型。
默认情况下,引擎支持许多 Qt C++ 类型,并能在从 QML 使用时自动进行适当转换。此外,在 QML 类型系统注册的 C++ 类可用作数据类型,如果适当注册,它们的枚举也可用作数据类型。更多信息,请参阅QML 和 C++ 之间的数据类型转换。
此外,数据从 C++ 转移到 QML 时,要考虑数据所有权规则。详见Data Ownership(数据所有权)。
公开属性
可使用Q_PROPERTY() 宏为任何QObject 衍生类指定属性。属性是一个类的数据成员,具有相关的读取功能和可选的写入功能。
QObject 衍生类或Q_GADGET 类的所有属性都可通过 QML 访问。
例如,下面是一个带有author
属性的Message
类。根据Q_PROPERTY 宏调用的指定,该属性可通过author()
方法读取,也可通过setAuthor()
方法写入:
注意: 请勿使用typedef或usingforQ_PROPERTY 类型,否则会使 moc 混淆。这可能会导致某些类型比较失败。
而不是
using FooEnum = Foo::Enum; class Bar : public QObject { Q_OBJECT Q_PROPERTY(FooEnum enum READ enum WRITE setEnum NOTIFY enumChanged) };
直接引用类型:
class Bar : public QObject { Q_OBJECT Q_PROPERTY(Foo::Enum enum READ enum WRITE setEnum NOTIFY enumChanged) };
为了使Message
可用,您需要在 C++ 中使用QML_ELEMENT ,在 CMake 中使用qt_add_qml_module。
class Message : public QObject { Q_OBJECT QML_ELEMENT Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged) public: void setAuthor(const QString &a) { if (a != m_author) { m_author = a; emit authorChanged(); } } QString author() const { return m_author; } signals: void authorChanged(); private: QString m_author; };
可以将Message
的实例作为必要属性传递给名为MyItem.qml
的文件,使其可用:
int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQuickView view; Message msg; view.setInitialProperties({{"msg", &msg}}); view.setSource(QUrl::fromLocalFile("MyItem.qml")); view.show(); return app.exec(); }
然后,可从MyItem.qml
读取author
属性:
// MyItem.qml import QtQuick Text { required property Message msg width: 100; height: 100 text: msg.author // invokes Message::author() to get this value Component.onCompleted: { msg.author = "Jonah" // invokes Message::setAuthor() } }
为了与 QML 实现最大程度的互操作性,任何可写属性都应该有一个相关的 NOTIFY 信号,只要属性值发生变化,该信号就会发出。这是 QML 的一项基本功能,它能在属性的任何依赖值发生变化时自动更新属性,从而加强属性之间的关系。
在上例中,author
属性的关联 NOTIFY 信号是authorChanged
,如Q_PROPERTY() 宏调用所指定。这意味着,每当该信号发出时(如在 Message::setAuthor() 中改变作者时),就会通知 QML 引擎必须更新涉及author
属性的绑定,反过来,引擎会通过再次调用Message::author()
来更新text
属性。
如果author
属性是可写的,但没有相关的 NOTIFY 信号,那么text
的值就会被初始化为Message::author()
返回的初始值,但不会随该属性以后的变化而更新。此外,任何从 QML 绑定到该属性的尝试都会产生引擎的运行时警告。
注: 建议将 NOTIFY 信号命名为<property>Changed(属性变更),其中<property>
是属性名称。无论相关 C++ 信号的名称如何,QML 引擎生成的相关属性更改信号处理器将始终采用on<Property>Changed
的形式,因此建议信号名称遵循此惯例,以避免任何混淆。
使用通知信号的注意事项
为防止循环或过度评估,开发人员应确保仅在属性值实际发生变化时才发出属性变化信号。此外,如果一个属性或一组属性不经常使用,则允许对多个属性使用相同的 NOTIFY 信号。这样做时应小心谨慎,以确保性能不受影响。
使用 NOTIFY 信号确实会产生少量开销。在某些情况下,属性值是在对象构造时设置的,随后不会更改。最常见的情况是当一个类型使用分组属性时,分组属性对象只分配一次,只有在删除对象时才释放。在这种情况下,可以在属性声明中添加 CONSTANT 属性,而不是 NOTIFY 信号。
CONSTANT 属性只能用于那些仅在类构造函数中设置值并最终确定值的属性。所有其他需要在绑定中使用的属性都应使用 NOTIFY 信号。
具有对象类型的属性
只要对象类型已在 QML 类型系统中注册,QML 就能访问对象类型属性。
例如,Message
类型可能有一个MessageBody*
类型的body
属性:
class Message : public QObject { Q_OBJECT Q_PROPERTY(MessageBody* body READ body WRITE setBody NOTIFY bodyChanged) public: MessageBody* body() const; void setBody(MessageBody* body); }; class MessageBody : public QObject { Q_OBJECT Q_PROPERTY(QString text READ text WRITE text NOTIFY textChanged) // ... }
假设Message
类型已在 QML 类型系统注册,允许 QML 代码将其用作对象类型:
Message { // ... }
如果MessageBody
类型也在类型系统中注册,就可以将MessageBody
分配给Message
的body
属性,而这一切都可以在 QML 代码中实现:
Message { body: MessageBody { text: "Hello, world!" } }
具有对象列表类型的属性
包含QObject 派生类型列表的属性也可以暴露给 QML。但为此目的,应该使用QQmlListProperty 而不是QList<T> 作为属性类型。这是因为QList 不是QObject-derived 类型,因此无法通过 Qt Qml 元对象系统提供必要的 QML 属性特征,如修改列表时的信号通知。
例如,下面的MessageBoard
类有一个QQmlListProperty 类型的messages
属性,它存储了Message
实例的列表:
class MessageBoard : public QObject { Q_OBJECT Q_PROPERTY(QQmlListProperty<Message> messages READ messages) public: QQmlListProperty<Message> messages(); private: static void append_message(QQmlListProperty<Message> *list, Message *msg); QList<Message *> m_messages; };
MessageBoard::messages() 函数只需从其QList<T>m_messages
成员创建并返回一个QQmlListProperty ,并根据QQmlListProperty 构造函数的要求传递适当的列表修改函数:
QQmlListProperty<Message> MessageBoard::messages() { return QQmlListProperty<Message>(this, 0, &MessageBoard::append_message); } void MessageBoard::append_message(QQmlListProperty<Message> *list, Message *msg) { MessageBoard *msgBoard = qobject_cast<MessageBoard *>(list->object); if (msg) msgBoard->m_messages.append(msg); }
请注意,QQmlListProperty 的模板类类型(本例中为Message
)必须在 QML 类型系统中注册。
分组属性
任何只读对象类型属性都可作为分组属性从 QML 代码中访问。这可用于公开一组相关属性,描述一个类型的一组属性。
例如,假设Message::author
属性的类型是MessageAuthor
,而不是简单的字符串,它的子属性是name
和email
:
class MessageAuthor : public QObject { Q_PROPERTY(QString name READ name WRITE setName) Q_PROPERTY(QString email READ email WRITE setEmail) public: ... }; class Message : public QObject { Q_OBJECT Q_PROPERTY(MessageAuthor* author READ author) public: Message(QObject *parent) : QObject(parent), m_author(new MessageAuthor(this)) { } MessageAuthor *author() const { return m_author; } private: MessageAuthor *m_author; };
可以使用 QML 中的分组属性语法写入author
属性,如下所示:
Message { author.name: "Alexandra" author.email: "alexandra@mail.com" }
作为分组属性公开的类型与对象类型属性的不同之处在于,分组属性是只读的,并在构建时被父对象初始化为有效值。分组属性的子属性可以从 QML 中修改,但分组属性对象本身永远不会改变,而对象类型属性可以随时从 QML 中分配一个新的对象值。因此,分组属性对象的生命周期受 C++ 父实现的严格控制,而对象类型属性可通过 QML 代码自由创建和销毁。
公开方法(包括 Qt 插槽)
QObject 派生类型的任何方法,只要符合以下条件,都可从 QML 代码中访问:
- 使用Q_INVOKABLE() 宏标记的公共方法
- 作为公共 Qt插槽的方法
例如,下面的MessageBoard
类有一个使用Q_INVOKABLE 宏标记的postMessage()
方法,以及一个作为公共槽的refresh()
方法:
classMessageBoard :publicQObject { Q_OBJECT QML_ELEMENTpublic: Q_INVOKABLEboolpostMessage(constQString&msg) { qDebug() << "Called the C++ method with" << msg; return true; }公共 槽:voidrefresh() { qDebug() << "Called the C++ slot"; };
如果MessageBoard
的实例被设置为文件MyItem.qml
的必填属性,那么MyItem.qml
就可以调用下面示例中的两个方法:
C++ | int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); MessageBoard msgBoard; QQuickView view; view.setInitialProperties({{"msgBoard", &msgBoard}}); view.setSource(QUrl::fromLocalFile("MyItem.qml")); view.show(); return app.exec(); } |
QML |
如果 C++ 方法有一个QObject*
类型的参数,参数值可通过对象id
或引用对象的 JavaScriptvar 值从 QML 传递。
QML 支持调用重载的 C++ 函数。如果有多个名称相同但参数不同的 C++ 函数,将根据所提供参数的数量和类型调用正确的函数。
当通过 QML 中的 JavaScript 表达式访问时,从 C++ 方法返回的值会转换为 JavaScript 值。
C++ 方法和 "this "对象
您可能想从一个对象获取 C++ 方法,然后在另一个对象上调用它。请看以下 QML 模块Example
中的例子:
C++ | |
QML | import QtQml import Example Invokable { objectName: "parent" property Invokable child: Invokable {} Component.onCompleted: child.invoke.call(this) } |
如果从合适的 main.cpp 中加载 QML 代码,应该会打印出 "invoked on parent"。然而,由于一个长期存在的错误,它并没有这样做。一直以来,基于 C++ 方法的 "this "对象与方法密不可分。改变现有代码的这种行为会导致微妙的错误,因为 "this "对象在很多地方都是隐含的。自 Qt 6.5 起,您可以显式地选择正确的行为,并允许 C++ 方法接受 "this "对象。为此,请在 QML 文档中添加以下 pragma:
pragma NativeMethodBehavior: AcceptThisObject
添加这一行后,上面的示例就能按预期运行了。
公开信号
QML 引擎会自动为从 QML 使用的QObject 派生类型的任何信号创建一个信号处理程序。信号处理程序总是命名为on<Signal>,其中<Signal>
是信号名称,第一个字母大写。信号传递的所有参数都可通过参数名称在信号处理程序中找到。
例如,假设MessageBoard
类有一个newMessagePosted()
信号,只有一个参数subject
:
class MessageBoard : public QObject { Q_OBJECT public: // ... signals: void newMessagePosted(const QString &subject); };
如果MessageBoard
类型已在 QML 类型系统中注册,那么在 QML 中声明的MessageBoard
对象就可以使用名为onNewMessagePosted
的信号处理器接收newMessagePosted()
信号,并检查subject
参数值:
MessageBoard { onNewMessagePosted: (subject)=> console.log("New message received:", subject) }
与属性值和方法参数一样,信号参数必须有 QML 引擎支持的类型;请参阅QML 和 C++ 之间的数据类型转换。(使用未注册的类型不会产生错误,但处理程序无法访问参数值)。
类可能有多个同名信号,但只有最后一个信号能作为 QML 信号访问。请注意,同名但参数不同的信号是无法区分的。
© 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.