Qt支持用C++语言编写自己的QML对象类型。得益于QML与Meta-object系统紧密结合,在QML程序中可以很容易的对QObject子类的属性、方法及信号进行访问。Qt提供的注册机制也比较简单,通过这个机制将C++编写的QML对象类型进行注册后,就可以在QML程序中使用自定义对象类型了。以下,我会结合实例讲解如何自定义QML对象类型以及如何对这些自定义对象类型进行注册。
一、对象类型定义
下面是一个C++定义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)
QML_ELEMENT
public:
// ...
};
在以上这个例子中,需要重点关注QML_ELEMENT宏,这个宏用于声明这个类可以在QML语言中使用,并且声明这个类在QML程序中对应的对象类型名称为Message。
与QML_ELEMENT宏对应的是QML_NAMED_ELEMENT宏,它与QML_ELEMENT宏功能类似,不同的是QML_NAMED_ELEMENT需要指定当前类在QML程序中对应的对象类型名称。例如,在以上示例中,可以将QML_ELEMENT替换为QML_NAMED_ELEMENT(CustomMessage),那么,这个类在QML程序中对应的对象类型名称为CustomMessage。(使用QML_ELEMENT及QML_NAMED_ELEMENT宏需要<QtQml/qqml.h>头文件)
对于一些复杂的对象类型,还需要借助其他一些宏进行声明。例如,如果想声明一个单例对象类型,需要使用QML_SINGLETON宏。下面是一个单例对象类型的示例:
class MySingleton : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
// Q_PROPERTY( ... )
public:
// members, Q_INVOKABLE functions, etc.
};
在以上示例中,定义了一个名为MySingleTon的QML对象类型。QML引擎会通过类的默认构造函数创建一个唯一的MySingleton对象。这个对象与QML引擎生命周期相同,当QML引擎被析构时,这个对象也会被析构。如果希望QML引擎使用我们创建的对象作为单例对象,那么需要定义一个工厂方法。在这个方法中返回一个单例对象。
class MySingleton : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
// Q_PROPERTY( ... )
public:
static MySingleton *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine)
{
MySingleton *result = nullptr;
// Create the object using some custom constructor or factory.
// The QML engine will assume ownership and delete it, eventually.
return result;
}
// members, Q_INVOKABLE functions, etc
};
在以上这个例子中,create是一个工厂函数,QML引擎通过调用这个函数获得单例对象。在使用单例对象时,我曾将犯过这样一个错误,在C++类中包含一个instance成员变量,又声明了一个静态get()函数返回这个instance对象。同时,C++类中还定义了一个默认构造函数。编写QML程序时,发现访问的不是C++类中的instance,后来我明白了,因为,QML中的用到的单例对象是QML引擎创建的另外一个对象而不是C++类中的instance。换句话说,这样定义的QML对象类型根本不是单例模式的。(如果用工厂函数替换get()函数就可以满足需求)。
有些时候,希望将一个已有类声明为一个QML对象类型,但是无法直接对这个类的源码进行修改(例如一些第三方库中包含的类)。对于这种情况,可以使用单独的一个结构体进行声明。在这个结构体中需要用QML_FOREIGN宏对这个类进行注册。以下是一个导出第三方类库中的类的示例:
struct Foreign
{
Q_GADGET
QML_FOREIGN(Immutable3rdParty)
QML_NAMED_ELEMENT(Accessible3rdParty)
QML_ADDED_IN_VERSION(2, 4)
// QML_EXTENDED, QML_SINGLETON ...
};
在这个结构体中,声明Immutable3rdParty为要导出的类,这个类在QML程序中的对象类型名称为Accessible3rdParty。
还有些时候,需要对一个已有类进行扩展并导出为QML对象类型,如果这个类的源码无法修改,那么需要借助一个扩展类型来实现扩展功能,并用QML_EXTENDED宏对扩展类型进行注册。以下是对扩展类注册的一个例子:
struct QLineEditForeign
{
Q_GADGET
QML_FOREIGN(QLineEdit)
QML_NAMED_ELEMENT(QLineEdit)
QML_EXTENDED(LineEditExtension)
};
在这个例子中,LineEditExtension是一个扩展类,当QML程序中对QLineEdit类型对象的新增属性进行访问时,QML引擎会将QLineEdit对象作为参数调用LineEditExtension构造函数创建一个对象,并对这个新增属性进行访问。
以上提到的这些宏都在qqmlregistration.h头文件中定义。
二、将C++类注册为QML对象类型
任何一个QOjbect类的子类都可以注册为QML对象类型。将一个QObject子类注册为QML对象类型需要进行如下操作:
(1)在类的定义中添加QML_ELEMENT或者QML_NAMED_ELEMENT(<name>)宏
(2)在.pro文件中添加 CONFIG += qmltypes
(3)在.pro文件中为QML_IMPORT_NAME变量指定一个命名空间名称。所有导出的QML类型将注册到这个命名空间中
(4)在.pro文件中为QML_IMPORT_MAJOR_VERSION变量指定一个主版本编号。
通过以上操作就可以将工程中的QObject子类注册到指定的命名空间中了。
以下是一个QML对象类型的注册示例:
//.pro文件片段
CONFIG += qmltypes
QML_IMPORT_NAME = com.mycompany.messaging
QML_IMPORT_MAJOR_VERSION = 1
//qml代码片段
import com.mycompany.messaging 1.0
Message {
author: "Amelie"
creationDate: new Date()
}
在这个例子中,Message是一个用C++语言编写的QML对象类型。QML_IMPORT_NAME 变量指定将这个类注册到com.mycompany.messaging命名空间中。在qml程序中通过import语句对com.mycompany.messaging模块引入,就可以访问这个模块中注册的QML对象类型了。(通过以上方式实际上也同时定义了一个名为com.mycompany.messaging的模块)。