将C++对象暴露给QML
例如,下面是包含 name 属性的 Person 类。正如 Q_PROPERTY 宏所指定那样,该属性可通过 name() 函数来读取,setName() 来写入:
为了访问实例中的数据,需要设置上下文属性,将数据暴露给由 QML 引擎实例化的 QML 组件。
通过调用 QQmlContext::setContextProperty() 来定义和更新上下文属性,允许以名称将数据显式地绑定到上下文。
view.engine()- rootContext()- setContextProperty("person", person); view.setSource(QUrl("qrc:/main.qml")); view.show(); return app.exec(); }
然后,就可以从 QML 中访问了:
为了尽可能增强与 QML 的交互性,任何可写的属性都应该有一个相关的 NOTIFY 信号,只要属性值发生改变就发出该信号。这允许该属性应用于属性绑定,属性绑定是 QML 的一个重要特性,每当其依赖的任何关系值发生改变,就会通过自动更新属性来强制执行属性之间的关系。
上述示例中,name 属性相关的 NOTIFY 信号是 nameChanged。这意味着无论何时发出该信号(就像在 Person::setName() 改变 name 时一样),将会通知 QML 引擎,必须更新涉及 name 属性的任何绑定。反过来,引擎将通过调用 Person::name() 来更新 text 属性。
如果 name 属性是可写的,但没有相关的 NOTIFY 信号,则 name 值将由 Person::name() 返回的初始值来初始化,但当该属性后续发生任何更改时不会进行相应的更新。此外,绑定到该属性的任何尝试都会产生运行时警告。
信号命名建议: 将 NOTIFY 信号命名为 property Changed 的形式,其中 property 是属性的名称。由 QML 引擎生成的关联的属性更改信号处理程序将始终采用 on Property Changed 的形式,而无需关心相关 C++ 信号的名称,因此建议信号名称遵循此约定,以避免任何混淆。
使用 Notify 信号的注意事项
为了防止循环或过度评估,应确保属性更改信号仅在属性值更改时发出。此外,如果一个属性或属性组不常被用到,则允许对若干属性使用相同的 NOTIFY 信号。不过,使用时应注意,确保性能不会受到影响。
NOTIFY 信号确实会有较小的开销。有时,有些属性的值仅在对象构造阶段设置,随后就再也不会改变。最常见的情况是当一个类型使用分组属性时,分组属性对象被分配一次,并且只有在销毁对象时才会释放。在这种情况下,属性声明时应该使用 CONSTANT 属性,而不是 NOTIFY 信号。
CONSTANT 属性应该仅用于那些仅在类构造函数中设值置,并且随后不再改变的属性,而所有可能会在绑定中使用的其他属性应该使用 NOTIFY 信号。
Q_PROPERTY(QString number READ number WRITE setNumber) Q_PROPERTY(QString validDate READ validDate WRITE setValidDate NOTIFY validDateChanged) public: void setNumber(const QString number) { if (number != m_number) m_number = number; QString number() const { return m_number; void setValidDate(const QString validDate) { if (validDate != m_validDate) { m_validDate = validDate; emit validDateChanged(); qDebug() "Valid date changed: " validDate; QString validDate() const { return m_validDate; signals: void validDateChanged(); private: QString m_number; // 身份证号码 QString m_validDate; // 有效日期 class Person : public QObject Q_OBJECT Q_PROPERTY(IDCard* idCard READ idCard WRITE setIDCard NOTIFY idCardChanged) public: IDCard* idCard() const { return m_idCard; void setIDCard(IDCard *idCard) { if (idCard != m_idCard) { m_idCard = idCard; emit idCardChanged(); qDebug() "ID card changed: " idCard- number(); signals: void idCardChanged(); private: IDCard *m_idCard; // 身份证 #endif // PERSON_H
任何 QObject 的派生类都可以被注册为 QML 对象类型。一旦使用 QML 类型系统注册,该类就可以像 QML 中的任何其他对象类型(例如:Item)一样被声明和实例化。一旦被创建,类实例便可以从 QML 中操作,作为 C++ 类型的属性暴露给 QML 解释,任何 QObject 派生类的属性、方法和信号都可以从 QML 代码访问。
要将 QObject 派生类注册为可实例化的 QML 对象类型,需要调用 qmlRegisterType() 将类注册为特定类型命名空间中的 QML 类型。然后客户端可以导入该命名空间,以便使用该类型。
例如,将 C++ 类型 Person 注册为名为 Person(双引号中的名称 - 尽量见名知义,对象类型首字母大写,例如:Per) 的 QML 类型,其在版本号为 1.0 的 People 命名空间中可用:
由于 IDCard 是 Person 的对象类型属性,所以要访问 IDCard 的附加信息,也需要被注册,完整的代码如下:
qmlRegisterType Person ("People", 1, 0, "Person"); qmlRegisterType IDCard ("People", 1, 0, "IDCard"); QQuickView view; view.setSource(QUrl("qrc:/main.qml")); view.show(); view.setIcon(QIcon(":/logo.png")); view.setTitle(QStringLiteral("将C++对象暴露给QML")); return app.exec(); }
一旦被注册,通过导入指定的类型命名空间和版本号便可以在 QML 中使用该类型:
anchors.centerIn: parent text: person.idCard.validDate // 先调用 Person::idCard() 来获取 IDCard,再调用 IDCard::validDate() Person { id: person idCard: IDCard { // 调用 Person::setIDCard() number: "610122..." // 调用 IDCard::setNumber() validDate: "2008.10.01-2018.10.01" // 调用 IDCard::setValidDate() }
这里,我们只显示身份证的有效日期,如下所示:
如果对象类型属性是只读的,则可以在 QML 中作为分组属性来访问,这种方式可用于暴露一个类型的一组相关属性。
修改上述示例:
分组属性:只读,只能在构造时由父对象初始化为一个有效值,生命周期由 C++ 父对象严格控制。其子属性可以从 QML 中修改,但是分组属性对象本身不能改变。 对象类型属性:可以随时被分配新的对象值,通过 QML 代码自由创建和销毁。 对象列表类型属性
如果属性包含 QObject 派生类列表,也可以暴露给 QML。但是,属性类型应该使用 QQmlListProperty,而非 QList T 。这是因为 QList 不是 QObject 的派生类型,因此不能通过 Qt 元对象系统提供必要的 QML 属性特性。例如,当列表被修改时的信号通知。
QQmlListProperty 是一个模板类,可以很方便的由一个 QList 值构造。
例如,Company 类有一个类型为 QQmlListProperty 的 persons 属性,存储了一个 Person 实例列表:
// person.h #ifndef PERSON_H #define PERSON_H #include QObject #include QQmlListProperty #include QDebug class Person : public QObject Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) public: void setName(const QString name) { if (name != m_name) { m_name = name; emit nameChanged(); qDebug() "Name changed: " name; QString name() const { return m_name; signals: void nameChanged(); private: QString m_name; // 姓名 // 公司 class Company : public QObject Q_OBJECT Q_PROPERTY(QQmlListProperty Person persons READ persons) public: Company(QObject *parent = 0); QQmlListProperty Person persons(); int personCount() const; Person *person(int) const; private: QList Person * m_persons; #endif // PERSON_H
注册同上:
// main.cpp qmlRegisterType Company ("People", 1,0, "Company"); qmlRegisterType Person ("People", 1,0, "Person"); ...
在 QML 中,我们定义一个 ListView 用于显示列表中人的姓名:
// main.qml import QtQuick 2.7 import QtQuick.Controls 2.0 import People 1.0 Rectangle { width: 200; height: 150 Component { id: contactDelegate Item { width: 180; height: 40 Row { spacing: 5 leftPadding : 5 topPadding : 5 bottomPadding : 5 Image { // 头像 source: "logo.png" sourceSize.width: 25 sourceSize.height: 25 Text { // 名字 text: name height: 25 horizontalAlignment : Text.AlignHCenter verticalAlignment : Text.AlignVCenter ListView { id: view anchors.fill: parent model: company.persons delegate: contactDelegate highlight: Rectangle { color: "lightsteelblue"; radius: 5 } focus: true Company { id: company persons: [ Person { name: "Qter" }, Person { name: "Pythoner" }, Person { name: "Linuxer" } // 输出信息 Component.onCompleted: { for (var i = 0; i company.persons.length; i++) console.log("Author: ", i, company.persons[i].name) }
效果如下:
注意: QQmlListProperty 的模板类类型 - 在这种情况下,是 Person - 必须向 QML 类型系统注册。
QML 可以访问 QObject 派生类的函数,但是函数需要满足以下条件之一:
现在,为人添加一些基本的行为。例如:eat、walk。。。吃饱了才有力气减肥,每天三万步,身体倍棒,吃嘛嘛香。
如果 C++ 函数的参数包含 QObject* 类型,参数值可以从 QML 中传递,使用一个对象 id 或引用该对象的一个 JavaScript var 值。
QML 支持重载的 C++ 函数调用,如果函数具有相同名称和不同参数,则将根据提供的参数的数量和类型调用正确的函数。
当从 QML 中的 JavaScript 表达式访问 C++ 函数时,函数的返回值将转换为对应的 JavaScript 值。
QML 引擎会为 QObject 派生类的信号自动地创建一个名为 on Signal 的信号处理程序,其中 Signal 是信号的名称,首字母大写。信号传递的所有参数在信号处理程序中都是可用的,通过参数名来访问。
前面,我们为人添加了走路的行为,其实三万步也是蛮多了。走完之后,人会发出一个信号:我累了,需要休息,具体的休息时间由参数 minute 决定:
QML 中声明的 Person 对象可以使用名为 onTired 的信号处理程序来接收 tired() 信号,并使用 minute 参数值:
与属性值和方法参数一样,信号的参数的类型也必须能够被 QML 引擎支持。值得注意的是,使用未注册的类型不会生成错误,但是参数值不能从处理程序中访问。
注意: 如果类中包含多个名称相同的信号,只有最后一个信号可以被 QML 访问,因为具有相同名称不同参数的信号无法被区分开来。
C++ 类 & 对象初学者学习笔记 面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。
C++对象生命周期 在C++中,对象的生命周期是指对象存在的时间段,从对象创建到对象销毁的整个过程。正确地管理对象的生命周期是编写高效、可靠C++代码的关键之一。
相关文章
- C++中各种容器特点总结
- C++关键字:mutable(转)
- C/C++基础讲解(六十四)之系统篇(删除目录树)
- C++连接mysql
- paip.提升性能--多核cpu中的java/.net/php/c++编程
- error: Microsoft Visual C++ 14.0 or greater is required. Get it with “Microsoft C++ Build Tools“:
- 【华为OD机试 2023】优雅子数组(C++ Java JavaScript Python)
- 【华为OD机试 2023】 优选核酸检测点(C++ Java JavaScript Python)
- 解答私信@被c++折磨头秃的花季美少女 //C++ 写一个带命令行参数的程序,可以实现将参数求和、求平均值以及排序之后输出(参数的数量不确定)。
- c++将字符数组转成字符串
- C++判断两个指针指向的对象是否相同
- C++建造者模式
- 关于C++ const 的全面总结
- C++对象模型之RTTI的实现原理
- C++标准输出流对象
- C++继承时的对象内存位置(一)有成员变量遮蔽时的内存分布
- C++通过引用来传递和返回类对象(四十二)
- C++11 静态断言—static_assert
- 从C和C++内存管理来谈谈JVM的垃圾回收算法设计-下
- C++自定义类的对象对于其私有变量的访问
- Dynamsoft Barcode Reader 9.0 for C++
- 黑马C++笔记——演讲比赛流程管理系统
- 学习C++:C++基础(一)类和对象及C++对C的扩展
- C++编程经验(8):对象优化,试试?试试就逝世哈哈哈
- C++使用技巧(二十八):回顾内存new关键字、引用、默认参数、 占位参数、重载、类和对象、构造函数