第三篇 使用C++扩展QML功能



使用C++扩展QML功能

分类:Qt QuickQML跨平台-QT2012-08-30 23:1436人阅读评论(0)收藏编辑删除

QML语法声明性的描述如何在内存中构建对象树.QtQML主要用于描述可视化场景图,但是其不仅限于此:QML格式可抽象描述任意对象树.QT中包含的所有QML元素类型都按本文中描述的机制由C++扩展而来的.开发者可以使用这些API函数扩展新的类型与Qt已存类型进行交互,或为特殊目更改QML.

添加类型

 import People 1.0

 

 Person {

     name: "Bob Jones"

     shoeSize: 12

 }

上面的QML片段定义了一个Person实例及其nameshoeSize属性.本质上QML中的每条语句都是在定义一个对象实例或设置属性的值.

QML依赖于Qt的元对象系统,仅可实例化QObject的子类.对于可视化元素类型,都是 QDeclarativeItem的子类;视图中元素的模型,都是 QAbstractItemModel的子类;任意带有属性的对象都是QObject的直接子类.

QML引擎不知道任何类型信息.需要程序员使用QML中所用的名称来注册C++.

自定义C++类型使用模版函数来注册:

 template<typename T>

 int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName)

调用qmlRegisterType()QML系统中注册类型T,QML中以qmlName引用类型,库的版本为versionMajor.versionMinor.qmlName可以与C++类型同名.

类型T必须是QObject的子类,必须有默认构造函数.

#include <QtDeclarative>才可调用qmlRegisterType().

类型可在库,应用程序代码,或插件中注册(QDeclarativeExtensionPlugin).

注册后,类中所有属性都在QML中可用.QML原生支持的属性类型列表在QML Basic Types 文档中描述,包括:

·        bool, unsigned int, int, float, double, qreal

·        QStringQUrlQColor

·        QDateQTimeQDateTime

·        QPointQPointFQSizeQSizeFQRectQRectF

·        QVariant

QML中元素是基于C++类支持的,当一个属性加入到C++,会自动生成一个对应的value-changed信号.见下面的 Signal Support .

QML是类型安全的.试图向属性赋非法值会生成一个错误.例如,Person元素的name属性是QString类型的,下面代码将引起错误:

 Person {

     // Will NOT work

     name: 12

 }

Extending QML - Adding Types Example 展示了创建Person类型的完整代码.

QML类型版本

C++中向类添加新的方法或属性不会影响原来的应用程序.然而在QML,新添加的方法或属性可能会改变以前的属性引用方式.

例如考虑到如下两个QML文件

 // main.qml

 import QtQuick 1.0

 Item {

     id: root

     MyComponent {}

 }

 // MyComponent.qml

 import MyModule 1.0

 CppItem {

     value: root.x

 }

CppItem映射到C++QCppItem.

如果QCppItem的作者在新版本模块中添加了一个新的属性root,将破坏上面程序中的root.x,使其指向了一个不同的值.解决方法是让QCppItem的作者做出声明,新的root属性只对特定版本的QCppItem生效.这样可以避免在已存在的元素中添加新属性和特性时破坏已有程序.

QML允许将属性,方法和信号与特定版本号绑定,只有特定版本的模块被导入时他们才能被访问.本例中,作者在子版本号为1的模型中添加了root属性,并注册模板版本为1.1.

REVISION标签标记root属性在子版本号为1的类中添加.Q_INVOKABLE方法,信号,槽也可以通过Q_REVISION(x)宏与子版本号绑定:

 class CppItem : public QObject

 {

     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(const char *uri, int versionMajor, int versionMinor, const char *qmlName)

注册MyModule模块1.1版本中的CppItem:

 qmlRegisterType<QCppItem,1>("MyModule", 1, 1, "CppItem")

root属性仅在导入MyModule的1.1版本时可用.

对象和列表属性类型

 BirthdayParty {

     host: Person {

        name: "Bob Jones"

        shoeSize: 12

     }

     guests: [

        Person { name: "Leo Hodges" },

        Person { name: "Jack Smith" },

        Person { name: "Anne Brown" }

     ]

 }

上面的QML片段将一个Person类型对象赋给BirthdayPartyhost属性,将三个Person对象赋给guests属性.

QML中可以设置比原始属性如整型和字符串的类型更复杂的属性.属性也可以是一个对象的指针,Qt接口指针,对象指针列表,Qt接口指针列表.QML是类型安全的可以确保只有合法的值才能赋给这些属性,和那些原生支持的类型一样.

指向对象或Qt接口的属性使用Q_PROPERTY()宏声明,与其他属性相同.host属性声明如下:

     Q_PROPERTY(Person *host READ host WRITE setHost)

只要属性的类型,本例中为Person,QML注册后就可以进行赋值.

QML也支持Qt的接口赋值.要给Qt接口指针类型的属性赋值,接口也需要在QML中注册.由于其不能直接实例化,注册接口与注册QML中新类型是不同的.要使用下面的函数:

 template<typename T>

 int qmlRegisterInterface(const char *typeName)

注册C++的接口T, QML中命名为typeName.

注册后,QML强迫实现接口的对象为相应类型的属性赋值.

guests属性是一个Person对象的列表.对象或接口列表属性也是用Q_PROPERTY()宏声明(与其他属性相同).列表属性必须是QDeclarativeListProperty<T>类型.而且类型T必须在QML中注册.

guests属性声明如下:

     Q_PROPERTY(QDeclarativeListProperty<Person> guests READ guests)

Extending QML - Object and List Property Types Example 范例展示了创建BirthdayParty类型的复杂代码.

继承和强制转换(Coercion)

 BirthdayParty {

     host: Boy {

        name: "Bob Jones"

        shoeSize: 12

     }

     guests: [

        Boy { name: "Leo Hodges" },

        Boy { name: "Jack Smith" },

        Girl { name: "Anne Brown" }

     ]

 }

上面的QML片段向BirthdayParty对象的host属性赋予了一个Boy对象值,并向guests属性赋予了三个其他对象.

QML支持C++的继承层次,并可自由的在类型间进行强制转换.可向声明为基类类型的对象或对象列表属性中赋予子类的实例.在这个片段中,guestshost都是Person类型的(属性或属性列表),但可赋值为从Person继承的BoyGirl对象的实例.

要给属性赋值,属性的类型必须在QML中注册.上面说是的qmlRegisterType()qmlRegisterInterface()都可向QML中注册类型.而对于作为基类的纯虚类型,是不能在QML中实例化的,也需要使用如下函数进行注册:

     template<typename T>

     int qmlRegisterType()

QML中注册C++中的纯虚类型T时qmlRegisterType函数没有任何参数,是要告诉qmlRegisterType()不为C++类和QML元素名称做映射,因此这个类型不能在QML中实例化,但可用于类型转换.

类型T必须继承于QObject,但这里不限制其是否拥有实际构造函数或构造函数签名.

当向对象属性或列表属性赋值时QML会自动转换C++类型.如果类型转换失败则抛出错误.

Extending QML - Inheritance and Coercion Example 范例中是生成BoyGirl类型的完整代码.

默认属性

 BirthdayParty {

     host: Boy {

        name: "Bob Jones"

        shoeSize: 12

     }

 

     Boy { name: "Leo Hodges" }

     Boy { name: "Jack Smith" }

     Girl { name: "Anne Brown" }

 }

上面的QML片段将一个对象列表赋值给BirthdayParty对象的默认属性.

默认属性是一个方便的语法,让类设计者可以指定一个属性作为类的默认属性.无论是否指定属性名称都可对默认属性赋值.可像指定属性名称一样对默认属性赋值,非常方便.

C++中类设计者使用Q_CLASSINFO()标记默认属性:

 Q_CLASSINFO("DefaultProperty", "property")

property 标记为类的默认属性property 必须是对象属性或列表属性.

默认属性是可选的.子类继承父类的默认属性,但可重新声明默认属性覆盖基类的设置property 可以为本类中定义的属性,也可是基类中定义的属性.

Extending QML - Default Property Example 展示定义默认属性的完整代码.

分组属性

     Boy {

        name: "Jack Smith"

        shoe {

            size: 8

            color: "blue"

            brand: "Puma"

            price: 19.95

        }

     }

上面的QMLBoy对象赋予一系列属性值,其中包括使用分组属性语法的四个属性.

分组属性(Grouped properties)将相似的属性组织在一个命名块中.分组属性可用来向开发者提供完美的API,通过实现重用,也可简化对跨类型的通用属性集合的实现.

分组属性块作为只读对象属性来实现.shoe属性定义如下:

     Q_PROPERTY(ShoeDescription *shoe READ shoe)

ShoeDescription定义了分组属性块中的属性,本例中为size,color,brandprice属性.

分组属性块可能会递归声明和存取.

Extending QML - Grouped Properties Example 展示shoe分组属性实现的完整代码.

附加(Attached)属性

     Boy {

        name: "Leo Hodges"

        shoe { size: 10; color: "black"; brand: "Reebok"; price: 59.95 }

 

        BirthdayParty.rsvp: "2009-07-06"

     }

上面的QML片段使用附加属性语法向rsvp属性赋予一个日期值.

附加属性是类型无关的,可为其他类型定义额外的说明性属性,便于内部使用.附加属性使用具体类型做标识,本例中使用BirthdayParty作为属性名称的前缀.

本例中,BirthdayParty叫做附加(attaching)类型,Boy叫做被附加(attachee)实例.

在附加类型中,附加的属性块被实现为一个新的QObject子类型,叫做附加对象(attachment object).附加对象中的属性可在附加属性块中访问.

任何声明了publicqmlAttachedProperties() 函数的QML类型,并具有QML_HAS_ATTACHED_PROPERTIES(附加属性),都可作为附加类型:

 class MyType : public QObject {

     Q_OBJECT

 public:

 

     ...

 

     static AttachedPropertiesType *qmlAttachedProperties(QObject *object);

 };

 

 QML_DECLARE_TYPEINFO(MyType, QML_HAS_ATTACHED_PROPERTIES)

为被绑定对象实例返回一个AttachedPropertiesType类型的附加对象.习惯上给附加对象赋予一个父对象可以防止内存泄露,但不是必须的.

AttachedPropertiesType 必须是QObject 的子类.本类型上的属性需要通过附加属性语法进行访问.

这个方法在每个被附加的对象实例上调用一次.QML引擎将缓存返回的实例指针给后面的附加属性访问.因此附加对象可能会保留到对象删除的时候.

概念上,附加的属性就是在一个类型中导出一系列的额外属性,可设置在其他对象实例上.附加属性并没有限制仅附加到对象实例上,但其产生的影响是这样限制的.

例如,一个通用场景是为类型增加可用属性,其下级项(children)可收集到更多实例数据.这里向所有出现生日派对的客人增加rsvp:

 BirthdayParty {

     Boy { BirthdayParty.rsvp: "2009-06-01" }

 }

然而,不能限制仅在需要附加信息的实例上附加属性,下面也是合法的,但这个上下文中增加birthday party rsvp是没有用处的.

 GraduationParty {

     Boy { BirthdayParty.rsvp: "2009-06-01" }

 }

C++,包含附加类的实现单元后,在实例中访问附加对象的方法为:

 template<typename T>

 QObject *qmlAttachedPropertiesObject<T>(QObject *attachee, bool create = true);

返回类型为T的附加到attachee (被附加对象)上的附加对象.如果T不是合法的附加类型,返回0.

如果create true,则总是返回一个可用的附加对象,如果不存在就创建一个新实例.如果create  false,附加对象仅在被创建时才返回.

Extending QML - Attached Properties Example 展示实现rsvp附加属性的完整代码.

内存管理和QVariant对象

这个单元负责确保不会返回或访问到指向非法对象的指针.QML做了如下保证:

·        赋值给QObject指针类型的属性的值必须在赋值的时候合法.

赋值后,这个类有责任使用类的特定方法或QPointer类来维护这个指针.

·        对象赋值给QVariant必须确保赋值时对象合法.

将一个对象赋值给QVariant属性,QML会使用QMetaType::QObjectStar 类型化QVariant.这个类负责保存指针.定义类中QVariant类型属性时通常需要在赋值时检查QVariant中值的具体类型,如果是不支持的类型,则重置为无效变量值.

·        对象赋值给一个QObject列表属性时必须确保赋值时对象合法.

赋值后,这个类有责任使用类的特定方法或QPointer类来维护这个指针.

必须假设任何被QML赋值的对象都有可能随时删除,并做相应处理.在场景中明确标明不需要继续运行的元素,就不会对程序造成影响.

信号支持

 BirthdayParty {

     onPartyStarted: console.log("This party started rockin' at " + time);

 }

上面的QML片段将一个JavaScript表达式的值与Qt触发的信号相关联.

所有类中声明的Qt信号都可在QML中作为特殊的信号属性,并可用一段JavaScript表达式进行赋值.信号属性名称为Qt信号名称的变形:Qt信号名称首字母大写,并加on前缀.例如,本例中的信号在C++中定义为:

 signals:

     void partyStarted(const QTime &time);

对于类中具有的多个同名信号,只有最后一个信号才作为信号属性.注意信号同名但参数不同是无法区分的.

在脚本中需要使用信号参数名称对其进行赋值.无名参数是不能访问的,因此在定义C++类的时候要给信号的所有参数带上名称.系统原生支持的信号参数类型请见Adding Types,同时注册的对象类型也可作为信号参数类型.使用其他类型也没有错误,只是不能在脚本中进行访问.

Extending QML - Signal Support Example 展示了实现onPartyStarted信号属性的完整代码.

如果要在不是由QML创建的对象上使用信号,可使用Connections来访问其信号.

另外,C++类中添加一个属性后,与这个C++类相对应的QML元素都会自动生成一个此属性的value-changed 信号.信号的名称为on<Property-name>Changed,属性名称的首字母大写.

注意: 这种QML信号总是命名为on<Property-name>Changed,即使是C++中的NOTIFY信号名称也一样.对于C++中的NOTIFY信号我们推荐使用<property-name>Changed()命名.

Importing Reusable Components

方法

使用Q_INVOKABLE标记的槽和函数可在QML中作为函数调用.

 BirthdayParty {

     host: Person {

        name: "Bob Jones"

        shoeSize: 12

     }

     guests: [

        Person { name: "Leo Hodges" },

        Person { name: "Jack Smith" },

        Person { name: "Anne Brown" }

     ]

 

     Component.onCompleted: invite("William Green")

 }

本例中使用可调用方法在BirthdayParty元素中添加一个调用约定(信号槽绑定).BirthdayParty类中使用Q_INVOKABLE将方法invite()标记为可在QML中调用:

     Q_INVOKABLE void invite(const QString &name);

Extending QML - Methods Example 展示实现invite()方法的完整代码.

invite()方法如果声明为槽函数也同样可用.

属性值源(Property Value Sources)

 BirthdayParty {

     HappyBirthdaySong on announcement { name: "Bob Jones" }

 }

上面的QML代码片段对announcement属性应用了属性值源HappyBirthdaySong.属性值源在属性发生改变时为属性生成新的值.

属性值源(Property value sources)通常用于在动画中.与其构造一个动画对象并手动设置动画的目标属性,使用属性值源(property value source)可以直接设置任意类型的属性并自动设置关联.

本例不是很自然:BirthdayParty对象的announcement 属性是字符串,并一直打印输出其值, HappyBirthdaySong 的属性源生成"Happy Birthday"歌曲的歌词.

     Q_PROPERTY(QString announcement READ announcement WRITE setAnnouncement)

通常,将字符串赋值给对象是非法的.在属性值源(property value source)情形下,不必给其赋值对象实例,QML引擎设置值源和属性的关联.

属性值源是从QDeclarativePropertyValueSource 类型继承的特殊类.这个基类只含有一个QDeclarativePropertyValueSource::setTarget()方法,QML调用这个方法设置属性值源与属性的关联.HappyBirthdaySong的相关定义如下:

class HappyBirthdaySong : public QObject, public QDeclarativePropertyValueSource

 {

     Q_OBJECT

     Q_INTERFACES(QDeclarativePropertyValueSource)

 public:

     HappyBirthdaySong(QObject *parent = 0);

 

     virtual void setTarget(const QDeclarativeProperty &);

 };

从其他方面看,属性值源(property value source)是规范的QML类型.必须像其他类型一样使用同样的宏向QML引擎进行注册,也可以包含属性,信号和方法.

当属性值源(property value source)对象赋值给属性,QML首先尝试做正常的赋值,因为它也是QML中的规则类型.如果赋值失败引擎才调用 setTarget()方法.因此这种类型也可像其他值一样用于上下文中.

Extending QML - Property Value Source Example展示实现HappyBirthdaySong 属性值源的完整代码.

属性绑定

 BirthdayParty {

     id: theParty

 

     HappyBirthdaySong on announcement { name: theParty.host.name }

 

     host: Boy {

        name: "Bob Jones"

        shoe { size: 12; color: "white"; brand: "Nike"; price: 90.0 }

     }

 }

上述的QML片段使用属性绑定设置HappyBirthdaySong属性与host始终同步.

属性绑定是QML的核心.相对于逐个赋值,属性绑定使开发者可以使用任意复杂的可包含其他属性值的JavaScript表达式进行赋值.当表达式的值变化时其中任意的一个成员值被改变,表达式自动重新计算并重新向属性赋值.

所有自定义类型属性都自动支持属性绑定.然而,为了让绑定正常工作,QML必须能够决定相关属性值何时被改变,以便于引擎重新计算依赖于此属性值的表达式.QML的这种判断能力依赖于NOTIFY signal关键字.

这是host属性的声明:

     Q_PROPERTY(Person *host READ host WRITE setHost NOTIFY hostChanged)

NOTIFY属性紧跟信号名称之后.这个类实现可确保当属性值变化时,NOTIFY信号总被触发.NOTIFY信号签名对QML很重要.

为防止循环或递归计算,开发者必须确保信号只有在值确实被修改时才触发.对于不常使用的属性或属性组,也可以使用同样的NOTIFY信号.但需要保证执行效率.

为保证QML可靠,如果属性没有NOTIFY信号,就不要用在绑定表达式中.然而这种属性也可以用在绑定中,QML不会监控属性值变化.

考虑一个自定义类型,TestElement,有两个属性ab.属性a没有NOTIFY信号,属性b具有NOTIFY信号.

TestElement {

     // This is OK

     a: b

 }

 TestElement {

     // Will NOT work

     b: a

 }

NOTIFY信号的存在需要引起注意.其使属性值在对象的构造期间被赋值,随后不会发生改变.这种场景常见于类中了组合属性(Grouped Properties),组合属性对象被一次性赋值,在对象删除时释放.这时,可能会使用CONSTANT属性替换NOTIFY信号.

     Q_PROPERTY(ShoeDescription *shoe READ shoe CONSTANT)

这种情况需要特别注意,应用程序中使用这个类可能会引起错误.CONSTANT属性(attribute)只能用于定义常量属性,其值只能在构造函数中设置.其他用在绑定中的属性必须具有NOTIFY信号.

Extending QML - Binding Example展示了BirthdayParty范例升级版本,包括用于绑定的NOTIFY信号.

扩展对象

 QLineEdit {

     leftMargin: 20

 }

上面的QML片段向C++已存类型中添加一个新的属性,但并没有更改源代码.

当向QML中整合已存代码和技术时,API通常需要做适当调整以便适合于声明语境的环境.当然最好是调整类的源代码,如果这不可行或很复杂,扩展对象可以避免直接修改类定义.

扩展对象通常用来向已存在的类中添加属性.扩展对象只能添加属性,不能添加信号和方法.扩展类型定义使程序员可创建一个额外类扩展类,注册后在QML中将目标类中额属性透明的合并到源类中.

扩展类是一个规则的QObject,构造函数接受一个QObject指针.需要时(扩展类型会在访问第一个扩展属性延时构造)扩展类被创建,目标对象指针传递给构造函数作为父对象.当原对象中访问扩展属性,就会访问扩展对象中相应的属性.

安装扩展类:

 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().参数与非扩展类注册函数一致,除了指定了扩展对象的ExtentedT参数.

优化

通常在开发高效的元素时了解QML引擎很有帮助.例如,延时初始化构造函数成本很高的数据,直到所有属性都被设置,就很有必要.

QML引擎定义了一个叫做QDeclarativeParserStatus接口类,其中包含了组件在实例化各个阶段中调用的数个虚函数.要接收这些通知,元素实现需要从QDeclarativeParserStatus继承,并使用Q_INTERFACES()宏通知Qt元数据系统.

例如,

 class Example : public QObject, public QDeclarativeParserStatus

 {

     Q_OBJECT

     Q_INTERFACES(QDeclarativeParserStatus)

 public:

     virtual void componentComplete()

     {

        qDebug() << "Woohoo!  Now to do my costly initialization";

     }

 };

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
QML可以使用C++动态库来实现更复杂的功能。通过在QML中调用C++动态库,我们可以利用C++的强大功能来处理一些复杂的逻辑和计算。下面是一个示例,演示了如何在QML使用C++动态库。 首先,在C++中创建一个类,该类将被注册到QML中。这个类可以包含一些函数和属性,供QML调用和访问。在这个例子中,我们创建了一个名为ColorMaker的类,它有一个函数用于生成随机颜色。 main.cpp文件中的代码如下[^1]: ```cpp #include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include "colormaker.h" int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); QQmlApplicationEngine engine; // 创建ColorMaker对象并注册到QML中 ColorMaker colorMaker; engine.rootContext()->setContextProperty("colorMaker", &colorMaker); const QUrl url(QStringLiteral("qrc:/main.qml")); engine.load(url); if (engine.rootObjects().isEmpty()) return -1; return app.exec(); } ``` 接下来,我们需要在QML中调用这个C++类。在QML中,我们可以使用`Qt.createQmlObject()`函数来创建一个C++对象,并调用其函数或访问其属性。在这个例子中,我们可以在QML中调用`colorMaker.generateRandomColor()`函数来生成一个随机颜色。 main.qml文件中的代码如下: ```qml import QtQuick 2.12 import QtQuick.Controls 2.12 import an.qt.ColorMaker 1.0 ApplicationWindow { visible: true width: 400 height: 300 title: "QML with C++" Button { text: "Generate Random Color" onClicked: { var color = Qt.createQmlObject('import an.qt.ColorMaker 1.0; ColorMaker {}').generateRandomColor(); console.log("Random color: " + color); } } } ``` 通过运行上述代码,我们可以在QML界面中点击按钮,调用C++动态库中的函数来生成随机颜色,并在控制台中打印出来。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值