当用c++代码扩展QML时,可以在QML类型系统中注册一个c++类,以便在QML代码中将该类用作数据类型。虽然任何qobject派生类的属性、方法和信号都可以从QML访问,正如在向QML公开c++类型属性中所讨论的那样,这样的类在向类型系统注册之前不能作为QML的数据类型。此外,注册还可以提供其他特性,例如允许将一个类作为QML中的一个可实例化的QML对象类型使用,或者允许将一个类的单例实例导入
1.注册c++类型与QML类型系统
可以向QML类型系统注册一个qobject派生类,使该类型能够在QML代码中用作数据类型。
该引擎允许注册可实例化和非实例化类型。注册一个可实例化的类型使c++类可以用作QML对象类型的定义,允许在QML代码的对象声明中使用它来创建这种类型的对象。注册还为引擎提供了额外的类型元数据,允许将类型(以及类声明的任何枚举)用作属性值、方法段的数据类型
下面提到的所有宏都可以从qqml.h头文件中获得。为了使宏可用,你需要将以下代码添加到使用它们的文件中:
#include <QtQml/qqml.h>
此外,类声明必须位于通过项目的include路径可达的头文件中。声明用于在编译时生成注册代码,注册代码需要包含包含声明的头文件。
1.1 注册可实例化对象类型
任何qobject派生的c++类都可以注册为QML对象类型的定义。一旦类在QML类型系统中注册,就可以像从QML代码中声明和实例化任何其他对象类型一样声明和实例化该类。一旦创建,就可以从QML操作类实例;正如向QML公开c++类型属性所解释的那样,任何qobject派生类的属性、方法和信号都可以从QML代码中访问。
要将qobject派生类注册为可实例化的QML对象类型,请将QML_ELEMENT或QML_NAMED_ELEMENT(<name>)添加到类声明和CONfig += qmltype
class Message : public QObject
{
Q_OBJECT
Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged)
Q_PROPERTY(QDateTime creationDate READ creationDate WRITE setCreationDate NOTIFY creationDateChanged)
QML_ELEMENT
public:
// ...
};
可以通过向项目文件添加适当的类型名称空间和版本号来注册此类型。例如,要使该类型在1.0版本的com.mycompany.messaging命名空间中可用:
CONfig += qmltypes
QML_IMPORT_NAME = com.mycompany.messaging
QML_IMPORT_MAJOR_VERSION = 1
如果类声明的头文件不能从项目的include路径访问,您可能需要修改include路径,以便编译生成的注册代码:
INCLUDEPATH += com/mycompany/messaging
该类型可以在QML的对象声明中使用,它的属性可以读取和写入,如下所示:
import com.mycompany.messaging 1.0
Message {
author: "Amelie"
creationDate: new Date()
}
1.2 注册Non-Instantiable类型
qobject派生类可能需要注册到QML类型系统,但不需要作为可实例化类型。例如,如果一个c++类:
1.接口类型是否不能实例化
2.一个基类类型,不需要公开暴露给QML
3.声明了一些应该可以从QML访问,但在其他情况下不应该实例化的枚举
4.是应该通过单例实例提供给QML的类型,并且不应该从QML实例化
Qt QML模块提供了几个宏来注册非实例化类型:
QML_ANONYMOUS 注册一个不可实例化的c++类型,不能从QML引用。这使引擎能够强制从QML实例化任何继承类型
QML_INTERFACE注册一个现有的Qt接口类型。该类型不能从QML实例化,并且不能用它声明QML属性。不过,从QML使用这种类型的c++属性可以实现预期的接口强制转换。
QML_UNCREATABLE(原因)与QML_ELEMENT或QML_NAMED_ELEMENT结合使用,向QML类型系统注册一个命名的c++类型,该类型不能实例化,但应该作为类型可识别。如果类型的枚举或附加属性应该可以从QML访问,但类型本身不应该是可实例化的,那么这是很有用的。如果检测到创建该类型实例的尝试,则该参数应该是一条错误消息。
QML_SINGLetoN与QML_ELEMENT或QML_NAMED_ELEMENT结合使用注册了一个可以从QML导入的单例类型,如下所述。
(注意,所有注册到QML类型系统的c++类型必须是qobject派生的,即使它们是非实例化的)
1.3 使用单例类型注册单例对象
单例类型允许在名称空间中公开属性、信号和方法,而不需要客户端手动实例化对象实例。特别是QObject单例类型是提供功能或全局属性值的一种有效且方便的方式。
注意,单例类型没有关联的QQmlContext,因为它们是在引擎中的所有上下文中共享的。QObject单例类型实例由qqmlenine构造并拥有,并且在引擎被销毁时被销毁。
QObject单例类型可以以类似于任何其他QObject或实例化类型的方式进行交互,除了只有一个(引擎构造和拥有)实例将存在,并且它必须通过类型名称而不是id引用。QObject单例类型的q_propertyys可以绑定到,QObject模块api的Q_INVOKABLE函数可以在信号处理程序表达式中使用。这使得单例类型成为实现样式化或主题化的理想方式,它们也可以用来代替"Pragma库脚本导入用于存储全局状态或提供全局功能。
一旦注册,就可以导入一个QObject单例类型,并像公开给QML的任何其他QObject实例一样使用它。下面的例子假设一个QObject单例类型在1.0版本中注册到“MyThemeModule”命名空间,其中QObject有一个QColor "color" Q_PROPERTY:
import MyThemeModule 1.0 as Theme
Rectangle {
color: Theme.color // binding.
}
QJSValue也可以作为单例类型公开,但是客户端应该意识到这种单例类型的属性不能被绑定。
有关如何实现和注册一个新的单例类型以及如何使用现有的单例类型的更多信息,请参阅QML_SINGLetoN。
注意:QML中注册类型的Enum值应该以大写开头。
1.4 注册可扩展的对象
当将现有的类和技术集成到QML中时,api通常需要调整以更好地适应声明式环境。尽管最好的结果通常是通过直接修改原始类获得的,但如果这是不可能的,或者因为其他一些问题而变得复杂,那么扩展对象允许有限的扩展可能性,而不需要直接修改。
扩展对象向现有类型添加附加属性。扩展对象只能添加属性,不能添加信号或方法。扩展类型定义允许程序员提供额外的类型
QLineEdit {
leftMargin: 20
}
leftMargin属性是添加到现有c++类型QLineEdit的新属性,不修改其源代码。
QML_EXTENDED(扩展)宏用于注册扩展类型。参数是要用作扩展名的另一个类的名称。
扩展类是常规的QObject,其构造函数接受QObject指针。但是,扩展类的创建被延迟,直到访问第一个扩展属性。创建扩展类,并将目标对象作为父对象传入。当原始属性上的属性被访问时,对应的属性
2 定义特定于qml的类型和属性
2.1 提供附加属性
在QML语言语法中,有一个附加属性和附加信号处理程序的概念,它们是附加到对象的附加属性。本质上,这些属性是由附加类型实现和提供的,这些属性可以附加到另一种类型的对象。这与由对象类型本身(或对象继承的类型)提供的普通对象属性形成了对比。
例如,下面的Item使用附加属性和附加处理程序:
import QtQuick 2.0
Item {
width: 100; height: 100
focus: true
Keys.enabled: false
Keys.onReturnpressed: console.log("Return key was pressed")
}
在这里,Item对象能够访问和设置Keys的值。和Keys.onReturnpressed启用。这允许Item对象作为其自身现有属性的扩展访问这些额外属性
2.2 实现附加对象的步骤
在考虑上述例子时,涉及到几个方面:
1.有一个匿名附加对象类型的实例,带有一个enabled和一个returnpressed信号,被附加到Item对象以使其能够访问和设置这些属
2.Item对象是附加对象,附加对象类型的实例已附加到该附加对象。
3.Keys是附加类型,它为附加对象提供了一个命名限定符“Keys”,通过它可以访问附加对象类型的属性。
当QML引擎处理此代码时,它会创建附加对象类型的单个实例,并将此实例附加到Item对象,从而为它提供对实例的enabled和returnpressed属性的访问。
提供附加对象的机制可以在c++中通过提供附加对象类型和附加类型的类来实现。对于附加的对象类型,提供一个从qobject派生的类,该类定义附加对象可访问的属性。对于附加类型,提供一个qobject派生类:
使用以下签名实现一个静态qmlAttachedProperties()
static <AttachedPropertiesType> *qmlAttachedProperties(QObject *object);
QML引擎调用此方法,以便将所附加对象类型的实例附加到由对象参数指定的附加对象上。习惯上(虽然不是严格要求),这个方法实现将返回的实例作为object的父实例,以防止内存泄漏。
引擎对每个附加对象实例最多调用一次此方法,因为引擎缓存了返回的实例指针,以便后续附加属性访问。因此,不能删除附件对象
2.3 实现附加对象:示例
例如,以前面示例中描述的Message类型为例:
class Message : public QObjectc
{
Q_OBJECT
Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged)
Q_PROPERTY(QDateTime creationDate READ creationDate WRITE setCreationDate NOTIFY creationDateChanged)
QML_ELEMENT
public:
// ...
};
假设有必要在消息发布到留言板时触发消息信号,并跟踪消息在留言板上何时过期。由于将这些属性直接添加到Message没有意义,因为这些属性与留言板上下文更相关,因此可以将它们实现为通过“MessageBoard”限定符提供的Message对象上的附加属性。根据前面所述的概念,此处涉及的各方为:
1.匿名附加对象类型的实例,它提供已发布信号和过期属性。此类型由下面的MessageBoardAttachedType实现
2.一个Message对象,它将是附件
3.MessageBoard类型,它将是消息对象用于访问附加属性的附加类型
下面是一个示例实现。首先,需要有一个附加的对象类型,带有被附加对象可以访问的必要属性和信号:
class MessageBoardAttachedType : public QObjectc
{
Q_OBJECT
Q_PROPERTY(bool expired READ expired WRITE setExpired NOTIFY expiredChanged)
QML_ANONYMOUS
public:
MessageBoardAttachedType(QObject *parent);
bool expired() const;
void setExpired(bool expired);
signals:
void published();
void expiredChanged();
};
然后,附加类型MessageBoard必须声明一个qmlAttachedProperties()方法,该方法返回由MessageBoardAttachedType实现的附加对象类型的实例。另外,MessageBoard必须通过QML_ATTACHED()宏声明为附加类型:
class MessageBoard : public QObject
{
Q_OBJECT
QML_ATTACHED(MessageBoardAttachedType)
QML_ELEMENT
public:
static MessageBoardAttachedType *qmlAttachedProperties(QObject *object)
{
return new MessageBoardAttachedType(object);
}
};
现在,Message类型可以访问附加对象类型的属性和信号:
Message {
author: "Amelie"
creationDate: new Date()
MessageBoard.expired: creationDate < new Date("January 01, 2015 10:45:00")
MessageBoard.onPublished: console.log("Message by", author, "has been
published!")
}
另外,c++实现可以通过调用qmlAttachedPropertiesObject()函数来访问附加到任何对象上的附加对象实例。
例如:
Message *msg = someMessageInstance();
MessageBoardAttachedType *attached =
qobject_cast<MessageBoardAttachedType*>(qmlAttachedPropertiesObject<MessageBoard>(msg));
qDebug() << "Value of MessageBoard.expired:" << attached->expired();
2.4 属性修改器类型
属性修饰符类型是一种特殊的QML对象类型。属性修饰符类型实例影响它所应用的(QML对象实例的)属性。有两种不同的属性修改器类型:
2.属性值来源
属性值写入拦截器可用于筛选或修改写入属性的值。目前,唯一支持的属性值写入拦截器是QtQuick导入提供的Behavior类型。
属性值源可用于随时间自动更新属性的值。客户端可以定义自己的属性值源类型。QtQuick导入提供的各种属性动画类型是属性值源的示例。
属性修饰符类型实例可以通过“<ModifierType> on <propertyName>”语法创建并应用于QML对象的属性,如下示例所示:
import QtQuick 2.0
Item {
width: 400
height: 50
Rectangle {
width: 50
height: 50
color: "red"
NumberAnimation on x {
from: 0
to: 350
loops: Animation.Infinite
duration: 2000
}
}
}
客户端可以注册自己的属性值源类型,但目前不能注册属性值写拦截器。
2.5 属性值来源
属性值源是QML类型,可以使用PropertyValueSource>在& lt; property>语法。例如,QtQuick模块提供的各种属性动画类型就是属性值源的例子。
通过继承QQmlPropertyValueSource并提供一个随时间向属性写入不同值的实现,可以在c++中实现属性值源。当属性值源使用PropertyValueSource>在& lt; property>在QML语法中,引擎会给它一个此属性的引用,以便更新属性值。
例如,假设有一个RandomNumberGenerator类作为属性值源可用,因此当应用于QML属性时,它将每500毫秒更新属性值为一个不同的随机数。另外,可以向这个随机数生成器提供一个maxValue。这个类可以这样实现:
class RandomNumberGenerator : public QObject, public QQmlPropertyValueSource
{
Q_OBJECT
Q_INTERFACES(QQmlPropertyValueSource)
Q_PROPERTY(int maxValue READ maxValue WRITE setMaxValue NOTIFY maxValueChanged);
QML_ELEMENT
public:
RandomNumberGenerator(QObject *parent)
: QObject(parent), m_maxValue(100)
{
QObject::connect(&m_timer, SIGNAL(timeout()), SLOT(updateproperty()));
m_timer.start(500);
}
int maxValue() const;
void setMaxValue(int maxValue);
virtual void setTarget(const QQmlProperty &prop) { m_targetProperty = prop; }
signals:
void maxValueChanged();
private slots:
void updateproperty() {
m_targetProperty.write(QRandomGenerator::global()->bounded(m_maxValue));
}
private:
QQmlProperty m_targetProperty;
QTimer m_timer;
int m_maxValue;
};
当QML引擎遇到使用RandomNumberGenerator作为属性值源时,它调用RandomNumberGenerator::setTarget()来为类型提供值源应用到的属性。当RandomNumberGenerator中的内部计时器每500毫秒触发一次时,它将向该指定属性写入一个新的数字值。
一旦RandomNumberGenerator类在QML类型系统中注册,就可以从QML中使用它作为属性值源。下面,它被用来每500毫秒改变一个矩形的宽度:
mport QtQuick 2.0
Item {
width: 300; height: 300
Rectangle {
RandomNumberGenerator on width { maxValue: 300 }
height: 100
color: "red"
}
}
在所有其他方面,属性值源都是常规的QML类型,可以具有属性、信号方法等,但附加的功能是,可以使用<PropertyValueSource>对<property>语法更改属性值。
当一个属性值源对象被分配给一个属性时,QML首先尝试正常地分配它,就像它是一个常规的QML类型一样。只有当这个赋值失败时,引擎才会调用setTarget()方法。这使得该类型除了作为值源之外,还可以用于上下文中。
2.6 为QML对象类型指定默认属性
任何注册为可实例化QML对象类型的qobject派生类型都可以有选择地为该类型指定默认属性。默认属性是一个对象的子对象自动分配的属性,如果它们没有分配给任何特定的属性。
默认属性可以通过调用具有特定“DefaultProperty”值的类的Q_CLASSINFO()宏来设置。例如,下面的MessageBoard类指定其messages属性作为类的默认属性:
class MessageBoard : public QObject
{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<Message> messages READ messages)
Q_CLASSINFO("DefaultProperty", "messages")
QML_ELEMENT
public:
QQmlListProperty<Message> messages();
private:
QList<Message *> m_messages;
};
这样,如果未将MessageBoard对象的子对象分配给特定属性,则可以将它们自动分配给其messages属性。例如:
MessageBoard {
Message { author: "Naomi" }
Message { author: "Clancy" }
}
如果没有将messages设置为默认属性,那么任何Message对象都必须显式地分配给messages属性,如下所示:
MessageBoard {
messages: [
Message { author: "Naomi" },
Message { author: "Clancy" }
]
}
(顺便说一句,Item::data属性是它的默认属性。添加到该数据属性的任何Item对象也会添加到Item::children的列表中,因此使用默认属性可以为项目声明可视化的子对象,而无需显式地将它们分配给children属性
2.7 用Qt Quick模块定义可视化项目
当使用Qt Quick模块构建用户界面时,所有可视化呈现的QML对象必须从Item类型派生,因为它是Qt Quick中所有可视化对象的基类型。这个Item类型由QQuickItem c++类实现,它是由Qt Quick模块提供的。因此,当需要在c++中实现可以集成到基于qml的用户界面中的可视化类型时,应该继承这个类。
有关更多信息,请参阅QQuickItem文档。另外,用c++编写QML扩展教程演示了如何在c++中实现基于qquickitem的可视化项目,并集成到基于Qt快速的用户界面中。
3 接收对象初始化的通知
对于一些自定义QML对象类型,将特定数据的初始化推迟到对象创建并设置其所有属性之后可能是有益的。例如,如果初始化代价很高,或者在初始化所有属性值之前不应该执行初始化,就可能会出现这种情况。
Qt QML模块提供了用于这些目的的子类化的QQmlParserStatus。它定义了许多虚方法,这些方法在组件实例化的不同阶段被调用。为了接收这些通知,一个c++类应该继承QQmlParserStatus并使用Q_INTERFACES()宏通知Qt元系统。
例如 :
class MyQmlType : public QObject, public QQmlParserStatus
{
Q_OBJECT
Q_INTERFACES(QQmlParserStatus)
QML_ELEMENT
public:
virtual void componentComplete()
{
// Perform some initialization here Now that the object is fully created
}
};