在C++中定义QML类型
当用C++代码对QML类型进行拓展时,可以利用QML的类系统对C++类进行注册,注册之后在QML的代码中就可以将这个类当做一个数据类型来使用。尽管在《QML获得C++类中的属性》一文中提到的任何QObject派生类的属性、方法和信号都可以在QML中获取到,但是除非将这样的一个类进行注册,否则该类就不能在QML中被当做一个数据类型来使用。此外,注册还可以提供其他的好处,比如允许一个类在QML中被当做一个可实例化的QML对象类型来使用或者允许在QML中导入或使用一个单例。
此外,Qt的QML模块提供了实现QML特点(比如附加属性)和C++基本特性的机制。
(注意:本文中提到的一些重要的概念将在《用C++写QML扩展》中进行证明。)
用QML类系统注册C++类型
利用QML类系统注册QObject派生类之后,在QML中就可以将该类当做一个数据类型来使用。
QML引擎可以允许注册可实例化和不可实例化的类。注册一个可实例化的类使得C++类可以被当作一个QML对象类来使用,允许在QML中创建这个类的对象。注册也给引擎提供了额外的元数据,允许这个类型(以及类中定义的枚举类型)被当作一个数据类型进行QML和C++之间的属性值、方法参数和返回值的交换。
注册一个不可实例化的类也是以这种方式,但是却不能在QML中被用来实例化为一个QML对象类型。这是很有用的,比如说一个类中有枚举类型需要被QML访问,但是这个类本身不能被实例化。
注册一个可实例化的对象类型
可以像定义一个QML对象类型那样注册任何QObject派生类。一旦这个类被注册之后,就可以像QML中的其他对象类型那样进行声明和实例化。一旦创建,一个类实例可以在QML中进行操作,正如在《QML获得C++类中的属性》一文中解释的那样,任何QObject派生类的属性、方法和信号都可以在QML中访问到。
为了将一个QObject派生类注册成为一个可实例化的QML对象类型,调用qmlRegisterType()将该类注册为QML类到一个特定的类命名空间。之后客户就可以通过导入这个命名空间来使用这个类型。
举个例子,假设有一个Message的类带有author和creationDate属性:
class Message : public OObject
{
Q_OBJECT
Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged)
Q_PROPERTY(QDateTime creationDate READ creationDate WRITE setCreationDate NOTIFY creationDateChanged)
public:
//...
};
这个类可以通过调用qmlRegisterType()来进行注册,同时带上命名空间和版本号。比如,将这个类注册到com.mycompany.messaging命名空间中,版本号为1.0:
qmlRegisterType<Message>("com.mycompany.messaging", 1, 0, "Message");
这个类可以在QML中被用于对象声明,并且它的属性可读可写,如下面的例子:
import com.mycompany.messaging 1.0
Message {
author : "Amelie"
creationDate : new Date()
}
注册不可实例化类型
有时一个QObject派生类可能需要利用QML类型系统注册成一个不可实例化的类型。举个例子,如果一个C++类有下列情况:
- 是一个不应实例化的接口类型
- 是一个不需要传递给QML的基类
- 定义了一些需要在QML中访问的枚举类型但其他方面不应实例化的
- 是一个通过单例提供给QML使用的类,并且不应在QML中进行实例化
Qt的QML模块提供了注册不可实例化类型的几种方法:
- qmlRegisterType()(不带参数)可以注册一个不可实例化的C++类,并且不能在QML中引用。这使得引擎可以控制任意可实例化的派生类。
- qmlRegisterInterface()可以用一个特定的QML类型名称注册一个Qt接口。这个接口在QML中是不可实例化的,但可以通过他的类型名引用到
- qmlRegisterUncreatableType()可以注册一个指定的不可实例化的C++类型,但这个类需要被QML类系统识别。当一个类的枚举类型或者附加的属性可以通过QML访问但类本身不可实例化时,这个方法很有用
- qmlRegisterSingletonType()可以注册一个单例类并且可以从QML中导入,就像下面讨论的那样:
注意所有利用QML类系统进行注册的C++类都必须继承于QObject类,即使他们是不可实例化的也不例外。
用单例类注册单例对象
一个单例类可以将属性、信号和方法暴露在命名空间中,而不需要客户手动地去实例化一个对象。QObject单例尤其是一个高效而方便的提供功能或者全局属性值的方法。
注意单例类型没有一个相关的QQmlContext,因为他们在引擎中被所有的上下文共享。QObject单例对象由QQmlEngine构造和拥有。,当引擎被销毁时也会跟着被销毁。
除了只有一个实例存在的情况(由引擎构建和拥有),一个QObject单例类型可以像其他QObject或可实例化类型那样相互影响,并且它必须通过类名而不是id进行引用。QObject的的单例类型可以绑定到信号处理器表达式中,QObject模块API中的Q_INVOKABLE函数也可以运用其中。这使得单例类型成为一个理想的实现样式和主题的方法,并且他们可以用在取代“.pragma library ”脚本导入来存储全局状态或提供全局功能。
一旦被注册,一个QObject单例类可以像其他QObject实例那样导入和使用。下面的例子假设一个QObject单例被注册进”MyThemeModule”的命名空间(1.0版本),这个单例有一个QColor类型的“color”属性:
import MyThememodule 1.0 as Theme
Rectangle{
color: Theme.color //binding.
}
一个QJSValue也可以被暴露为一个单例类,但客户应该意识到这样一个单例类的属性不能被绑定。
类型修正和版本
许多的类型注册函数需要给被注册的类型指定版本号。类型修正和版本号允许新的特性和方法存在于新的版本中,同时保持与之前版本的兼容。
思考下面的两个QML文件:
// main.qml
import QtQuick 1.0
Item {
id: root
MyType {}
}
// MyType.qml
import MyTypes 1.0
CppType{
value: root.x
}
其中CppType与C++类CppType对应,如果CPPtype的作者添加了一个root属性到一个新的版本中,root.x现在成为了一个不同的值,因为root也是顶级组件的id,作者这时候可以指定从某个子版本开始这个root属性才可以使用。这样就可以为现有类添加新的属性和特点时,不影响现存的程序。
REVISION标签用于标记root属性是在修正版1中被加入的。例如Q_INVOKABLE标记的函数、信号、槽也可以用Q_REVISION(x)宏标记为一个修正版本:
class CppType : public BaseType
{
Q_OBJECT
Q_PROPERTY(int root READ root WRITE setRoot NOTIFY rootChanged REVISION 1)
signals:
Q_REVISION(1) void rootChanged();
};
为了将新的类修正注册到一个特定的版本中,要用到下面的函数:
template<typename T, int metaObjectRevision>
int qmlRegisterType(cosnt char* uri, int versionMajor, int versionMinor, const char* qmlName)
为了将CppType版本1注册到MyTypes 1.1:
qmlRegisterType<CppType, 1>("MyTypes", 1, 1, "CppType")
只有当MyTypes的1.1版本被导入的时候root才起作用。
同样的原因,后续版本引入的新类型应该修改qmlRgisterType的子版本参数。
这个语言的特点允许在行为发生改变时不影响现有的程序。因此,QML模块的作者要注意记录不同版本间的改动,同时要注意在引入新的版本后,现有的程序依然可以正确运行。
你也可以通过qmlRegisterRevision()函数注册你的类型的基类的版本:
template<typename T, int metaObjectRevision>
int qmlRegisterRevision(const char *uri, int versionMajor, int versionMinor)
template<typename T, int metaObjectRevision>
int qmlRegisterUncreatableType(const char *uri, int versionMajor, int versionMinor, const char *qmlName, const QString& reason)
template<typename T, typename E, int metaObjectRevision>
int qmlRegisterExtendedUncreatableType(const char *uri, int versionMajor, int versionMinor, const char *qmlName, const QString& reason)
举个例子,如果BaseType被改变了并且现在有一个修正版1,你可以指定你的类型用新的版本:
qmlRegisterRevision<BaseType,1>("MyTypes",1,1);
当从其他作者提供的基类继承比如从Qt Quick模块扩展类时这个特性就比较有用了。
注意:QML引擎不支持成组或附加的属性对象的版本修订。
注册扩展对象
当把存在的类和技术集成到QML中时,API经常需要进行修改以更好地融入声明环境中,即使通过直接修改原始类可以获得最好的结构,如果这种方法既不可行也太复杂,扩展对象可以在不进行直接修改的情况下获得有限的扩展。
扩展对象会添加新的属性到类中。扩展对象只能添加属性,信号和方法则不行。当注册类时,一个扩展类允许编程者提供一个额外的类,称作扩展类。在QML中使用时,这些属性可以显而易见地和原始的对象类结合在一起。举个例子:
QLineEdit {
leftMargin: 20
}
在没有修改源代码的情况下,leftMargin属性成为了QLineEdit类中的新属性。
qmlRegisterExtendedType()函数是用来注册扩展类的,注意它有两种形式:
template<typename T, typename ExtendedT>
int qmlRegisterExtendedType(const char *uri, int versionMajor, int versionMinor, const char *qmlName)
template<typename T, typename ExtendedT>
int qmlRegisterExtendedType()
在进行注册时,应该使用这些函数而不是常规的qmlRegisterType。其中的参数除了扩展对象类型参数ExtendedT,其他都是相同的。
一个扩展类是常规的QObject,带有包含QObject指针的构造函数。但是,扩展类的创建会被推迟到第一个扩展属性被访问的时候。扩展类创建的时候目标对象会被当做父类传入。当访问原始类的属性的时候,实际访问的是扩展类中对应的属性。
参见 扩展对象例子。