QML与C++混合编程(三)在C++中定义QML类型

在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指针的构造函数。但是,扩展类的创建会被推迟到第一个扩展属性被访问的时候。扩展类创建的时候目标对象会被当做父类传入。当访问原始类的属性的时候,实际访问的是扩展类中对应的属性。

参见 扩展对象例子。

猜你喜欢

转载自blog.csdn.net/guimaxingtian/article/details/81100822
今日推荐