从C ++定义QML类型
QML类型系统
在QML文档中的对象层次结构定义中可以使用的类型可以来自各种来源。它们可能是:
- 由QML语言本地提供
- QML模块通过C ++注册
- 由QML模块作为QML文档提供
基本类型
基本型是一种指的是简单的值,例如一个int
或一个string
。这与QML对象类型相反,QML对象是指具有属性,信号,方法等的对象。基本类型与对象类型不同,不能用于声明QML对象:例如,不能声明int{}
对象或size{}
对象。
支持的基本类型
引擎默认支持某些基本类型,不需要使用import语句,而其他一些则需要客户端导入提供它们的模块。下面列出的所有基本类型都可以用作QML文档中的类型,但以下情况除外:
list
必须与QML对象类型结合使用enumeration
不能直接使用,因为枚举必须由已注册的QML对象类型定义
QML语言提供的基本类型
以下列出了QML语言本身支持的基本类型:
Binary true/false value | |
Number with a decimal point, stored in double precision | |
Named enumeration value | |
Whole number, e.g. 0, 10, or -20 | |
List of QML objects | |
Number with a decimal point | |
Free form text string | |
Resource locator | |
Generic property type |
QML模块提供的基本类型
QML模块可以使用更多基本类型扩展QML语言。例如,QtQuick
下面列出了模块提供的基本类型:
Date value | |
Value with x and y attributes | |
Value with x, y, width and height attributes | |
Value with width and height attributes |
基本类型的属性更改行为
一些基本类型具有属性:例如,字体类型具有pixelSize
,family
和bold
特性。与对象类型的属性不同,基本类型的属性不提供自己的属性更改信号。只能为基本类型属性本身创建属性更改信号处理程序:
Text {
// invalid!
onFont.pixelSizeChanged: doSomething()
// also invalid!
font {
onPixelSizeChanged: doSomething()
}
// but this is ok
onFontChanged: doSomething()
}
//属性中的基本类型发生改变,会调用 属性改变操作
Text {
onFontChanged: console.log("font changed")
Text { id: otherText }
focus: true
// changing any of the font attributes, or reassigning the property
// to a different font value, will invoke the onFontChanged handler
Keys.onDigit1Pressed: font.pixelSize += 1
Keys.onDigit2Pressed: font.b = !font.b
Keys.onDigit3Pressed: font = otherText.font
}
JavaScript类型
QML引擎支持JavaScript对象和数组。可以使用通用var类型创建和存储任何标准JavaScript 类型。
import QtQuick 2.0
Item {
property var theArray: [] //创建数组
property var theDate: new Date() //创建日期
Component.onCompleted: {
for (var i = 0; i < 10; i++)
theArray.push("Item " + i)
console.log("There are", theArray.length, "items in the array")
console.log("The time is", theDate.toUTCString())
}
}
QML对象类型
QML对象类型是可以从中实例化QML对象的类型。QML对象类型是从QtObject派生的,并由QML模块提供。应用程序可以导入这些模块以使用它们提供的对象类型。该QtQuick
模块提供了在QML中创建用户界面所需的最常见的对象类型
QML对象类型是可以实例化QML对象的类型。
用语法术语来说,QML对象类型是一种可以用来声明对象的方法,方法是指定类型名称,后跟一组包含该对象属性的花括号。这与基本类型不同,基本类型不能以相同的方式使用。例如,Rectangle是QML对象类型:可用于创建Rectangle
类型对象。使用诸如int
和的原始类型无法完成此操作,这些原始类型bool
用于保存简单的数据类型而不是对象。
可以通过创建一个定义类型的.qml文件来定义自定义QML对象类型,如在文档中作为QML对象类型定义所讨论的那样,也可以通过从C ++定义QML类型并向QML引擎注册该类型来进行定义,如定义QML中所述来自C ++的类型。请注意,在两种情况下,类型名称都必须以大写字母开头,以便在QML文件中声明为QML对象类型。
使用QML定义类型
要创建对象类型,应将QML文档放入名为<TypeName> .qml的文本文件中,其中<TypeName>是所需的类型名称。类型名称具有以下要求:
- 它必须由字母数字字符或下划线组成。
- 它必须以大写字母开头。
然后,引擎会自动将该文档识别为QML类型的定义。另外,在解析QML类型名称时,与引擎在直接目录中搜索时一样,以这种方式定义的类型将自动提供给同一目录中的其他QML文件
// SquareButton.qml
import QtQuick 2.0
Rectangle {
id: root
property bool pressed: mouseArea.pressed
signal buttonClicked(real xPos, real yPos)
function randomizeColor() {
root.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
}
property int side: 100
width: side; height: side
color: "red"
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: root.buttonClicked(mouse.x, mouse.y)
}
}
//使用
// application.qml
import QtQuick 2.0
SquareButton {
id: squareButton
onButtonClicked: {
console.log("Clicked", xPos, yPos)
randomizeColor()
}
Text { text: squareButton.pressed ? "Down" : "Up" }
}
从C ++定义对象类型
注册实例化对象类型
任何QObject派生的C ++类都可以注册为QML对象类型的定义。
要将QObject派生的类注册为可实例化的QML对象类型,请调用qmlRegisterType()将该类作为QML类型注册到特定的类型名称空间中。然后,客户端可以导入该名称空间以使用该类型。
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)
public:
// ...
};
//使用qmlRegisterType注册C++类,就可以在QML文档中使用
qmlRegisterType<Message>("com.mycompany.messaging", 1, 0, "Message");
//在qml中调用
import com.mycompany.messaging 1.0
Message {
author: "Amelie"
creationDate: new Date()
}
注册非实例类型
有时,可能需要向QML类型系统注册QObject派生的类,而不是将其注册为可实例化的类型。例如
- 接口类型
- 基类
- 声明的枚举
- 单例
在Qt的QML模块提供用于注册非实例类型的几种方法:
- qmlRegisterType()(无参数)注册无法实例化且无法从QML引用的C ++类型。这使引擎能够强制转换QML实例化继承自基类的类型。
- qmlRegisterInterface()注册现有的Qt接口类型。该类型不能从QML实例化,并且不能使用它声明QML属性。但是,从QML使用这种类型的C ++属性将执行预期的接口强制转换。
- qmlRegisterUncreatableType()注册一个命名的C ++类型,该类型不可实例化,但应标识为QML类型系统的类型。如果应从QML访问类型的枚举或附加属性,但该类型本身不可实例化,则此方法很有用。
- qmlRegisterSingletonType()注册可以从QML导入的单例类型,如下所述。
请注意,向QML类型系统注册的所有C ++类型都必须是QObject派生的,即使它们不是不可实例化的。
用单例类型注册单例对象
//假设MyThemeModule 已经注册了单例类型
import MyThemeModule 1.0 as Theme
Rectangle {
color: Theme.color // binding.
}
具体单例类型的定义见https://doc.qt.io/qt-5/qqmlengine.html#qmlRegisterSingletonType
类型修订和版本
许多类型注册功能要求为注册的类型指定版本。类型修订版和版本允许新属性或方法存在于新版本中,同时保持与先前版本的兼容性。
// main.qml
import QtQuick 1.0
Item {
id: root
MyType {}
}
// MyType.qml
import MyTypes 1.0
CppType {
value: root.x
}
//在版本1.1中添加root属性
class CppType : public BaseType
{
Q_OBJECT
Q_PROPERTY(int root READ root WRITE setRoot NOTIFY rootChanged REVISION 1)
signals:
Q_REVISION(1) void rootChanged();
};
qmlRegisterType<CppType,1>("MyTypes", 1, 1, "CppType")
注册扩展对象
https://doc.qt.io/qt-5/qtqml-referenceexamples-extended-example.html
可以在现有的类上添加扩展功能
示例 https://code.qt.io/cgit/qt/qtdeclarative.git/tree/examples/qml/referenceexamples/extended?h=5.14
定义特定于QML的类型和属性
以下示例实现了附加属性
import QtQuick 2.0
Item {
width: 100; height: 100
focus: true
Keys.enabled: false //附加属性
Keys.onReturnPressed: console.log("Return key was pressed")
}
实现附加对象的步骤
考虑以上示例时,涉及多个方面:
- 有一个匿名附加对象类型的实例,带有
enabled
和returnPressed
信号,该实例已附加到Item对象,以使其能够访问和设置这些属性。 - 将附加对象的实例附加到item上。
- Keys提供了通过限定符,“Keys”访问的属性附加对象类型
当QML引擎处理此代码时,它将创建附加对象类型的单个实例,并将该实例附加到Item对象,从而为其提供对实例的enabled
和returnPressed
属性的访问。
C++签名对象使用以下语法:
static <AttachedPropertiesType> *qmlAttachedProperties(QObject *object);
//此方法应返回attached object type(定义了要附加的属性和信号)的实例。
引擎为每个附加对象实例最多调用一次此方法,因为引擎会缓存返回的实例指针以用于后续的附加属性访问。因此,只有在object销毁附件后才能删除附件对象。
通过调用带有QML_HAS_ATTACHED_PROPERTIES标志的QML_DECLARE_TYPEINFO()宏将其声明为附加类型
附加对象例子
在Message添加附加对象
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)
public:
// ...
};
要添加附加对象需要:
- 一个匿名的attached object type实例,提供
published
信号和expired 属性的附加对象类型的实例。该类型由MessageBoardAttachedType
以下实现 - 一个
Message
对象,将成为附加对象 MessageBoard
类型,这将是附接类型所使用的Message
对象来访问附加的属性
//定义一个要附加的对象实例,里面定义了要添加的信号和属性
class MessageBoardAttachedType : public QObject
{
Q_OBJECT
//过期属性
Q_PROPERTY(bool expired READ expired WRITE setExpired NOTIFY expiredChanged)
public:
MessageBoardAttachedType(QObject *parent);
bool expired() const; //
void setExpired(bool expired);
signals:
void published(); //发布信号
void expiredChanged();
};
class MessageBoard : public QObject
{
Q_OBJECT
public:
static MessageBoardAttachedType *qmlAttachedProperties(QObject *object)
{
return new MessageBoardAttachedType(object);
}
};
QML_DECLARE_TYPEINFO(MessageBoard, QML_HAS_ATTACHED_PROPERTIES)
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!")
}
附加属性分三个步骤:
- 创建一个要属性加属性的类,实现所有要添加的属性和信号。例MessageBoardAttachedType
- 要把MessageBoardAttachedType定义为附加属性还需要做MessageBoard 干的事,实现qmlAttachedProperties函数返回MessageBoardAttachedType ,并使用QML_DECLARE_TYPEINFO(MessageBoard, QML_HAS_ATTACHED_PROPERTIES)
- 把MessageBoard 定义为附加属性。
- 现在就可以在Message使用MessageBoard做为句柄访问MessageBoardAttachedType 中的属性的信号了。
使用C++代码访问附加属性使用
Message *msg = someMessageInstance();
MessageBoardAttachedType *attached =
qobject_cast<MessageBoardAttachedType*>(qmlAttachedPropertiesObject<MessageBoard>(msg));
qDebug() << "Value of MessageBoard.expired:" << attached->expired();
属性修改器
属性修改器分为两种
- 是属性写拦截器,不能注册,现在只有 Behavior在使用拦截器
- property value sources,
属性修改器可以使用 "<ModifierType> on <propertyName>"语法实现,例
import QtQuick 2.0
Item {
width: 400
height: 50
Rectangle {
width: 50
height: 50
color: "red"
NumberAnimation on x { //属性修改器,循环修改x的值
from: 0
to: 350
loops: Animation.Infinite
duration: 2000
}
}
}
客户端可以注册自己的属性值源类型,但当前不能注册属性值写拦截器。
属性值源
属性值源可以随时间自动更新属性值。使用<PropertyValueSource> on <property>
语法。动画就是使用属性源来实现的
使用C++实现需要继承 QQmlPropertyValueSource 例:
//使用修改器必继承QQmlPropertyValueSource
class RandomNumberGenerator : public QObject, public QQmlPropertyValueSource
{
Q_OBJECT
Q_INTERFACES(QQmlPropertyValueSource) //?
Q_PROPERTY(int maxValue READ maxValue WRITE setMaxValue NOTIFY maxValueChanged);
public:
RandomNumberGenerator(QObject *parent)
: QObject(parent), m_maxValue(100)
{
QObject::connect(&m_timer, SIGNAL(timeout()), SLOT(updateProperty()));
m_timer.start(500); //500毫秒调用一次updateProperty
}
int maxValue() const;
void setMaxValue(int maxValue);
//初始化要修改的属性,具体属性是在qml中使用on指定的
virtual void setTarget(const QQmlProperty &prop) { m_targetProperty = prop; }
signals:
void maxValueChanged();
private slots:
void updateProperty() {
//将On后的属性值改为随机生成的值
m_targetProperty.write(QRandomGenerator::global()->bounded(m_maxValue));
}
private:
QQmlProperty m_targetProperty;
QTimer m_timer;
int m_maxValue;
};
使用示例
import QtQuick 2.0
Item {
width: 300; height: 300
Rectangle {
RandomNumberGenerator on width { maxValue: 300 } //随机修改rect的宽度(0-300之间的数),
height: 100
color: "red"
}
}
为QML对象类型指定默认属性
C++定义默认属性
class MessageBoard : public QObject
{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<Message> messages READ messages)
Q_CLASSINFO("DefaultProperty", "messages") //定义一个默认属性
public:
QQmlListProperty<Message> messages();
private:
QList<Message *> m_messages;
};
使用一个默认属性
MessageBoard {
Message { author: "Naomi" }
Message { author: "Clancy" }
}
不使用默认属性
MessageBoard {
messages: [
Message { author: "Naomi" },
Message { author: "Clancy" }
]
}
使用Qt Quick模块定义可视化组件
C++类需要继承QQuickItem,来开发可视化组件,“ 用C ++编写QML扩展”教程演示了如何在C ++中实现基于QQuickItem的可视项并将其集成到基于Qt Quick的用户界面中。
接收对象初始化通知
如果想在组件加载完成后进行初始化,可以继承QQmlParserStatus ,并使用Q_INTERFACES()进行注册
class MyQmlType : public QObject, public QQmlParserStatus
{
Q_OBJECT
Q_INTERFACES(QQmlParserStatus)
public:
virtual void componentComplete()
{
// Perform some initialization here now that the object is fully created
}
};