QML 自定义对象类型

QML 自定义对象类型

QML 中自定义一种新的对象类型有两种方法:

  • QML Document (也称组件文件 Component file)
  • C++ create, QML engine register

1. Define type through QML Document

官方文档: http://doc.qt.io/qt-5/qtqml-documents-definetypes.html

这种方法最简单了,QML 语法规定,每个 QML 文件都定义一个 QML 对象,它的文件名就是对象类型了。文件名首字母必须大写,可以用字母,数字和下划线 (推荐“驼峰命名”)。随后,QML 引擎就会自动将它定义为 QML 对象类型。比如我们创建一个 MyButton.qml 文件,那么应用中就可以使用 MyButton 对象类型了。

tip: 存放该 QML 文件的文件夹中的其他 QML 文件都可以直接使用该 QML 类型,无需 import 该文件

问: 对象类型的实例可以使用 哪些属性 (attributes) ?

答: 这个问题涉及到 property scope 的概念

  1. public scope 包括顶层对象(root item) 所有的 properties, signals, methods 属性 (不包括 id)。它们既可以是顶层对象自定义的属性 (custom attributes),也可以是来自顶层对象实例对应的对象类型的属性。
  2. private scope 包括所有子对象 (child items) 和它们的所有属性 (attributes),只能在 该 QML Document 中才能使用
  3. 所有的 id 都属于 private scope,id values are only accessible from within the component scope in which a component is declared.

回答问题: 对象类型的实例 只能使用 public scope 的属性

编程建议:

  1. 通过一个额外的 QtObject 实例对外部隐藏一些属性,实现类似 C++ 的 private 的功能
  2. 有的时候,我们不希望继承 顶层对象的对象类型的公共属性,所以一般推荐顶层对象类型为 Item, 完美隐藏组件的实现细节,只暴露我们的接口,比如下面这样:

此外,通过 Component 对象类型可以实现匿名对象类型 (只有 id , 而没有具体的对象类型), 参考我的另外一篇博客 – QML Component

2. C++ 创建,QML 引擎注册

官方文档:

  1. Exposing Attributes of C++ Types to QML
  2. Defining QML Types from C++
  3. Data type conversion between QML and C++

推荐官方 tutorial: Writing QML Extensions with C++

Qt 提供两种 C++ 继承技术

  1. 将 C++ 对象实例注册到 QML
    该对象实例对于 QML 引擎来说是 全局变量

  2. 将 C++ 对象类型 注册到 QML
    在 QML 环境可以创建该对象类型的实例

    • 可实例化 (instantiable) 的对象类型
      任何继承自 QObject 的 C++ 类都可以注册为 QML 对象类型。在 QML 对象类型中,可以直接使用 signal, property, method 属性
      注册函数: qmlRegisterType()

    • 不可实例化(non-instantiable) 的对象类型,一般 C++ class 是下面四种类型

      • 接口类型 (interface type)
      • 抽象基类 (base class type),只有它的子类可以实例化
      • QML 只需要该类声明的枚举类型,而不需要其他部分
      • QML 环境中只有一个实例化对象 (singleton instance)

Qt 提供如下函数

  • qmlRegisterType() (不含参数) 这种方式 C++ 类型既不能实例化,也不能被引用,但允许 QML 引擎使用它的子类
  • qmlRegisterInterface() 注册 Qt 接口类型
  • qmlRegisterUncreatableType() 将 C++ 类中的枚举变量暴露给 QML 环境,但 class 本身不能被实例化也不能被引用
  • qmlRegisterSingletonType() 注册单例类型

Note: 所有注册到 QML 类型系统的 C++ 类都必须继承自 QObject

2.1 Exposing Attributes of C++ Types to QML

QML 引擎依赖于 Qt 元对象系统 (meta-object system) ,通过该系统,QML 引擎可以 深入到 C++ 的 QObject 实例内部。QML 引擎可以接触到 继承自 QObject 的类的如下成员:

  1. properties (declared with Q_PROPERTY )
    C++ 中,property 通常对应着一个 private 变量,只读变量可以只实现 READ 方法,可写变量必须实现 READ 和 WRITE 方法。通常为了和 QML 的 property bind 机制兼容,一般也需要实现 NOTIFY signal 发送 (无论什么时候, property 的值改变,都必须发送该信号)
    Note: 推荐将 NOTIFY signal 命名为 <property>Changed ,其中 <property> 是该属性的名字。原因是 QML 默认每个 property 对应的 property change signal handler 的名字是采取 on<Property>Changed 的形式 (实际上这个 signal handler 不存在,只是一个约定)。同时 QML 还会自动给每个 signal 生成一个相联系的 signal handler (这个 signal handler 是真实存在的),名字总是采取 on<Signal> 的形式。如果我们将 signal 命名为 <property>Changed ,就不会和 QML 的默认机制混淆 。
    NOTIFY signal 的存在确实会带来一定的开销。对于某些只在对象构造阶段设置值随后不会改变的 property,建议在 Q_PROPERTY 中标记为 CONSTANT ,同时不用实现 NOTIFY signal

  2. methods (public slots or flagged with Q_INVOKABLE)

  3. signals
    只要知道 QML 会自动将 signal 连接到 on<Signal> 的 signal handler 就好了

  4. enumeration (declared with Q_ENUM )
    Note: 如果 该枚举类型用于 method 或 signal 的参数,则需要通过 qRegisterMetaType() 注册

Note: 如何在 C++ 中设计 Object type Object-list type 的 property 以及 grouped property , attached type property ?

Object Type 类型的 property 在 C++ 中是对应类型的指针

上面这个例子,class Message 包含一个 property 类型为 MessageBody * ,名称为 body, 那么在 QML 中,我们可以设置如下:

grouped property 和 object-type property 实现的不同:

上面的代码中,我们在 Message 的 construction function 就初始化了 m_author 变量 (类型为 MessageAuthor ),同时只设置了 author property READ 方法,没有设置 WRITE 方法。Message 实例初始化以后,我们永远不能改变 指针 m_author 的地址,但我们可以改变 指针指向的 MessageAuthor 实例的子属性 – nameemail

所以 QML 是这么设置的:

这和上面的 body object-type property ( MessageBody 类型) 在 QML 的使用完全不同,它是完全实例化一个 MessageBody 对象,然后赋值给 body property。具体 C++ 实现,则是不同人为初始化变量,同时设置 READ 和 WRITE 方法

Message {
    body: MessageBody {
        .....
    }
}

我的个人理解:

对于某些必不可少的 property,应该用 grouped property。我们必须直接在构造函数初始化,但是可以定制 property 的子属性,最典型的例子就是 font 了。每个 组件都需要 font 这个属性,干脆我们在 组件初始化就给它一个默认的 font 属性,然后后续时候,我们可以调节 font 的每个子属性,比如字体类型,大小,粗细等。

而对于可有可无的 property,应该用 object property。比如 itemchildren property,每个组件是否包含 子组件都是未知的,

对于 Object-list type,通过 QQmlListProperty class 实现

上面中,变量存储为 QList<Message *> 类型,但属性为 QQmlListProperty<Message> 类型,我们实现 该类型的 添加 方法,并实现 property 的 READ 方法

2.2 QML 和 C++ 数据类型转换

由于 C++ 是强类型语言,因此在设计 C++ class 时,我们必须指定它的 property 类型 , method 的参数类型和返回值类型,signal 的参数类型。同时,我们必须考虑 QML 中数据类型 和 C++ 中数据类型的相互转换。

基本类型 (basic type)

Qt Type QML Basic Type
bool bool
unsigned int, int int
double double
float, qreal real
QString string
QUrl url
QColor color
QFont font
QDate date
QPoint, QPointF point
QSize, QSizeF size
QRect, QRectF rect
QMatrix4x4 matrix4x4
QQuaternion quaternion
QVector2D, QVector3D, QVector4D vector2d, vector3d, vector4d
Enums declared with Q_ENUM() enumeration

前面的博客提到过在 QML 如何初始化基本类型,

  1. 直接设置字符串值,QML 引擎会自动帮我们转成对应的类型,比如 color : "red" ,虽然 “red” 是字符串,QML 引擎在解析时,会把它转为对应的 QColor 类型。
  2. 通过 QtQml::Qt 全局对象,它提供一系列的基本类型初始化函数

继承自 QObject 的对象类型 (object type)

对于 继承自 QObject 的自定义 class 类,通过 qmlRegisterType 注册到 QML 引擎中,专门有一篇博客将通过 C++ 实现自定义的 QML 对象类型。

Note: QML 中的 JavaScript 环境修改了原生的 JavaScript 原型链 (object type) 以提供额外的特性

C++ JavaScript
QVariantList Array
QVariantMap Object
QDateTime, QTime Date
QByteArray ArrayBuffer

如果对象从 C++ 传入 QML,对象的持有者总是 C++。唯一的例外是从 C++ 方法返回的 QObject 类型,这种情况下,QML 引擎是该对象的持有者,QML 通过垃圾回收实现内存释放。除非我们利用 QQmlEngine::setObjectOwnership() 同时用 QQmlEngine::CppOwnership 参数 显式地设置对象的持有者仍然是 C++。但是从 Q_PROPERTY 返回的 QObject * 对象仍然是属于 C++ 的

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d 博主赞过: