zl程序教程

您现在的位置是:首页 >  后端

当前栏目

将C++对象暴露给QML

C++对象 qml 暴露
2023-09-11 14:19:13 时间
QML 可以很容易地通过 C++ 代码中定义的功能进行扩展。由于 QML 引擎与 Qt 元对象系统的紧密集成,QObject 派生类适当暴露的任何功能都可以从 QML 代码访问,这使得 C++ 中的数据和函数可以直接从 QML 中访问,通常不需要太多修改,甚至不用修改。 通过元对象系统,QML 引擎具有内省 QObject 实例的能力。这意味着,任何 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++代码的关键之一。