您的位置:首页 > 产品设计 > UI/UE

Qt Quick QML

2015-12-22 11:33 886 查看
Qt Quick简介

Qt Quick是一个用于帮助开发者设计直观,现代,流畅的用户界面的技术集,近年来被广泛应用于手机,媒体播放器,机顶盒和其他手提设备.Qt Quick中包含了大量的用户界面元素,及描述这些用户界面的陈述性(declarative)语言,和一个语言运行时.在典型的Qt应用程序中有一系列C++ API与这些高层次特性整合.Qt Creator2.1的整合开发环境(IDE)包含了开发Qt Quick应用程序的必要工具.



QML语言

QML是高层次的描述性语言.其命令和得体的元素可以平衡Qt库的功能和效率,使用可执行直观功能的命令非常容易.绘制一个矩形,展示图片,及应用程序事件--都可以在这个陈述性(declarative)程序中完成.

这种语言也允许使用JavaScript灵活控制这些命令,实现高层次的用户界面逻辑.

为方便定义元素通常QML元素都带有各种属性.例如,如果要定义一个圆,那么可能就有一个半径作为其属性.使用导入的元素构建用户界面是QML和Qt Quick的一大特点.

QtDeclarative模块

为创建Qt Quick,Qt引入了QtDeclarative模块.模块创建了一个JavaScript运行时, Qt在后端支持QML的运行.由于QtDeclarative和QML都基于Qt构建的,他们继承了很多Qt技术,即信号槽(signals and slots) 机制和元对象(meta-object)系统.在C++中创建的数据对象可直接在QML中访问,QML对象也可直接在C++代码中进行操作.

QtDeclarative模块协调QML语言,将界面逻辑与C++的应用逻辑相隔离.

Creator工具

Qt Creator是一个完整的基于Qt Quick和Qt应用程序框架的整合开发工具(IDE).



Qt Creator的主要目的是满足Qt Quick开发者的需要,使其简单,易用,高效,可扩展,开放,降低Qt和Qt Quick新人的入门门槛.Qt Creator的主要特性是允许UI设计人员和开发者完成如下任务:

§ 使用范例和教程及项目向导可快速学会使用Qt Quick开发应用程序.

§ 使用整合的编辑器Qt Quick Designer设计应用程序用户界面,或使用绘图软件设计用户界面在使用脚本将设计导入到Qt Quick Designer.

§ 使用高级的代码编辑器开发应用程序,提供了强大的代码完成功能,重构代码功能,及查看QML文件元素继承层次功能.

§ 开发针对多种桌面或移动平台的Qt Quick应用程序,如Microsoft Windows, Mac OS X, Linux, Symbian, 和 Maemo.

§ 在当前上下文中调试JavaScript函数并执行JavaScript表达式,在运行时查看QML的对象结构,动画调试信息和颜色信息.

§ 向移动设备部署应用程序并为Symbian和Maemo设备创建安装包,通过Ovi存储设备和其他通道发布.

§ 可与上下文敏感的QT帮助系统整合轻松访问帮助信息.

可跳转页面

Qt Quick页面链接了各种Qt Quick主题如QML features, addons, 和tools.

QML Examples and Demos页面是学习QML应用程序的捷径.

授权信息

§ Qt Quick Licensing Information

QML语法

QML是一种声明式的语言,用来描述程序的用户界面:两个方面--外观和行为.在QML中用户界面被描述为带有属性的对象的树.

QML中使用JavaScript作为脚本语言,因此深入学习QML前应先多了解一下Javascript.

QML基本语法

QML是这个样子的:

import QtQuick 1.0

Rectangle {

width: 200

height: 200

color: "blue"

Image {

source: "pics/logo.png"

anchors.centerIn: parent

}

}

声明对象时,先指定其类型,后面是一对大括号.对象类型名称的首字母总是大写的.上例中,有两个对象,一个Rectangle,一个Image.在大括号中间可以设置对象的信息,如属性.

设置属性使用 propertyname: value的方式.上例中,设置了Image对象的source属性值为 "pics/logo.png".属性和值中间用冒号间隔.

属性可在一行设置:

Rectangle {

width: 100

height: 100

}

或在一行中设置多个属性:

Rectangle { width: 100; height: 100 }

当在一行中设置多个属性,需要使用分号间隔.

import语句导入Qt模块,其中包含了所有标准的QML元素.没有导入语句,Rectangle和Image元素都是非法的.

表达式

也可将JavaScript表达式赋值给属性.

Rotation {

angle: 360 * 3

}

表达式中可以包含其他对象和属性的引用,这样就建立了一种绑定.当表达式的值变化时,对应的属性值会自动更新.

Item {

Text {

id: text1

text: "Hello World"

}

Text {

id: text2

text: text1.text

}

}

上例中,text2显示的内容总与text1相同.如果text1被修改了,text2也会自动修改为同样的值.

注意通过id属性指定的对象名称来引用其他对象 (更多id属性信息见下面的描述.)

QML注释

QML中的注释与JavaScript相同.

单行注释使用//.

多行注释使用 /* */

Text {

text: "Hello world!" //a basic greeting

/*

We want this text to stand out from the rest so

we give it a large size and different font.

*/

font.family: "Helvetica"

font.pointSize: 24

}

注释被引擎忽略.用于解释做了什么,便于日后查阅,也有助于其他人阅读QML文件.

注释也可防止代码执行,便于跟踪问题.

Text {

text: "Hello world!"

//opacity: 0.5

}

上例中,Text对象具有正常的透明度,因为opacity: 0.5被注释掉了.

QML元素

这是Qt Quick子部分的QML元素的功能分组列表.

These are the functionally grouped lists of QML elements as part of Qt Quick.

声明元素时要带有名称和两个花括号.元素可能被嵌入到其他元素中,从而在两个元素间创建了父子关系.

Elements are declared with the their name and two curly braces. Elements may be nested in elements, thereby creating a parent-child relationship between the two elements.

要查看QML元素的功能列表,请见Groups Of Related QML Elements

To see the QML elements listed by functional area, see the Groups Of Related QML Elements page.

基本QML元素

Item -被其他QML元素继承的基本项 Basic item element inherited by QML elements

Component - 导入时封装了QML元素Encapsulates QML elements during importing

QtObject - 仅容纳一个objectName属性的基本元素Basic element containing only the objectName property

图形

Rectangle - 一个矩形元素A rectangle element

Image -屏幕上的一个位图 For incorporating bitmaps into a scene

BorderImage - 可使用图片作为边缘 Allows the use of images as borders

AnimatedImage - 未播放动画而存储的一系列帧 For playing animations stored in a series of frames

Gradient - 定义颜色渐变 For defining a color gradient

GradientStop - 为Gradient定义一个颜色 Used to define a color within a Gradient

SystemPalette - 提供访问Qt调色板的接口 Provides access to the Qt palettes

文字处理

Text - 向屏幕上插入格式化的文字 For inserting formatted text into a scene

TextInput - 获取用户键盘输入 Captures user key input

TextEdit - 显示多行可编辑的格式化文本 Displays multiple lines of editable formatted text

IntValidator - 验证整数Validates values as integers

DoubleValidator - 验证实数 Validates real values

RegExpValidator - 字符串正则表达式验证器 Validator for string regular expressions

FontLoader - 按名称或URL加载字体 Loads fonts by name or URL

鼠标和交互区域

MouseArea - 设置一块用于鼠标交互的区域 Sets up an area for mouse interaction

Keys - 提供一个带有附加属性的组件用于处理键盘输入Provides components with attached properties to handle key input.

FocusScope - 传递键盘焦点变化的元素 Element that mediate keyboard focus changes

Flickable - 提供实现"flicked"(弹性)效果的表面组件 Provides a surface that can be "flicked"

Flipable - 提供处理”flipping”(翻转)效果的表面组件 Provides a surface that produces "flipping" effects

PinchArea - 简单的挤压手势处理 Enables simple pinch gesture handling

定位器和重复器(Repeater)

Column - 垂直排列子元素 Arranges its children vertically

Row - 水平排列子元素 Arranges its children horizontally

Grid - 在网格中定位子元素 Positions its children in a grid

Flow - 按坐标定位子元素 Positions its children with wrapping support

Repeater - 使用一个模型创建多个组件 Uses a model to create multiple components

转换Transformations

Scale - 设置item的缩放行为 Assigns item scaling behaviors

Rotation - 设置item的选择行为 Assigns item rotation behaviors

Translate - 设置item的移动行为 Assigns item translation behaviors

状态States

State - 定义对象和属性的一系列配置值 Defines sets of configurations of objects and properties

PropertyChanges - 描述在状态中的属性变化 Describes property changes within a state

StateGroup - 包含一系列的状态和状态变换 Contains a set of states and state transitions

StateChangeScript - 时脚本绑定到一个状态上 Allows script binding in a state

ParentChange - 在状态改变时重定义item的父项 Re-parent an Item in a state change

AnchorChanges - 在状态中改变item的描点 Change the anchors of an item in a state

动画和过度(Transitions)

Transition - 状态改变时动画的过度 Animates transitions during state changes

SequentialAnimation - 顺序执行动画 Runs animations sequentially

ParallelAnimation - 并行执行动画 Runs animations in parallel

Behavior - 为属性变换指定默认动画 Specifies a default animation for property changes

PropertyAction - 在动画中设置直接的属性变化 Sets immediate property changes during animation

PauseAnimation - 暂停动画 Introduces a pause in an animation

SmoothedAnimation - 使属性平滑的到达指定值 Allows a property to smoothly track a value

SpringAnimation - 使属性向弹簧效果一样达到指定值 Allows a property to track a value in a spring-like motion

ScriptAction - 动画时执行脚本 Runs scripts during an animation

基于数据类型的元素动画属性 Elements that animate properties based on data types

PropertyAnimation - 基于属性变化的动画 Animates property changes

NumberAnimation - 基于greal类型属性的动画 Animates properties of type qreal

Vector3dAnimation - 基于QVector3d类型属性的动画 Animates properties of type QVector3d

ColorAnimation - 基于颜色属性的动画 Animates color changes

RotationAnimation - 基于旋转的动画 Animates rotations

ParentAnimation - 基于parent属性变化的动画 Animates parent changes

AnchorAnimation - 基于描点变化的动画 Animates anchor changes

模型和数据处理 Models and Data Handling

ListModel - 定义数据列表 Defines a list of data

ListElement - 在ListModel中定义数据项 Defines a data item in a ListModel

VisualItemModel - 包含定义在其可视化代理中的项 Contains items that already defines its own visual delegate

VisualDataModel - 封装一个模型和一个代理 Encapsulates a model and a delegate

XmlListModel - 使用XPath表达式指定一个模型 Specifies a model using XPath expressions

XmlRole - 为XmlListModel指定一个角色 Specifies a role for an XmlListModel

Binding - 在任意属性上绑定任意值 Binds any value to any property

Package - 可在不同视图中共享的项的集合 Collection that enables sharing of items within different views

视图Views

ListView - 提供一个可视化列表模型Provides a list visualization of a model

GridView - 提供一个可视化网格模型 Provides a grid visualization of a model

PathView - 假设模型的内容中含有一个路径.更多信息见Path Elements.Visualizes a model's contents along a path. See Path Elements for more information.

路径定义Path Definition

Path - 使用PathView定义一个路径 Defines a path used by PathView

PathLine - 在Path中定义一条线 Defines a line in Path

PathQuad - 在Path中定义一条二次方的贝塞尔曲线 Defines a quadratic Bezier curve in a Path

PathCubic - 在Path中定义一条三次方的贝塞尔曲线 Defines a cubic Bezier curve in a Path

PathAttribute - 允许设置Path的属性 Allows the setting of attributes along a Path

PathPercent - 修改Path中item的分布 Modifies the item distribution along a Path

功能Utility

Connections - 显示连接信号和槽 Explicitly connects signals and signal handlers

Timer - 提供定时器Provides timed triggers

Qt - QML中全局的Qt对象提供Qt中有用的枚举类型和函数.The QML global Qt object provides useful enums and functions from Qt.

WorkerScript - 可在QML中使用线程 Enables the use of threads in QML

Loader - 控制item或组件的加载 Controls the loading of items or components

LayoutItem - 允许在Qt的Graphics View布局中声明UI元素 Allows declarative UI elements inside Qt's Graphics View layouts

图像效果 Graphical Effects

粒子(Particles) - 生成并播放粒子动画Generates and animates particles

ParticleMotionLinear - 向粒子增加直线运动行为 Adds linear motion behavior to Particles

ParticleMotionGravity - 向粒子增加自由落体运动行为 Adds gravitational motion to Particles

ParticleMotionWander - 向粒子增加不同的运动行为Adds varied motions to Particles

ShaderEffectItem - 允许在QML中使用OpenGL Shading Language(OpenGL阴影描述语言) Enables the use of OpenGL Shading Language together with QML

ShaderEffectSource - 封装QML对象树作为ShaderEffectItem 的元item Encapsulates QML item tree as a source item for ShaderEffectItem

扩展元素 Add-On Elements

这些元素不包括在QtQuick1.0模块中.要使用他们必须先获取并安装.These elements are not included in the QtQuick 1.0 module. Their respective QML bindings should first be obtained and installed.

QtWebKit QML模块 - WebView元素 - 显示web内容

Mobility QML Plugins

Qt Quick Components

使用QML视图显示数据

视图是包含项目的集合.他们富有特色,可自定义风格和行为

Qt Quick图形元素提供了几个标准的视图:

ListView 水平或垂直列表中排列项目

GridView 在一个有效空间的网格内排列项目

PathView 在路径上排列项目

WebView - 可在QtWebKit QML Module中使用.

与其他视图不同,WebView 不具有全部视图特性,需要与Flickable组合创建一个像Web浏览器一样执行的项目.

这些元素具有的属性和行为相互独立.更多信息见他们的文档.

模型

视图在屏幕上显示模型(models).模型可以是简单的整形列表或一个C++模型.

要给视图设置模型,需要给视图的model属性绑定到一个模型.

ListModel {

id: petlist

ListElement { type: "Cat" }

ListElement { type: "Dog" }

ListElement { type: "Mouse" }

ListElement { type: "Rabbit" }

ListElement { type: "Horse" }

}

ListView {

id: view

anchors.fill: parent

model: petlist

delegate: petdelegate

}

更多信息见QML Data Models 文档.

视图代理

视图需要使用代理(delegate)来可视化的表现列表中的项.视图以代理作为模版显示列表中的每个项.模型中的项使用index属性来访问.

Component {

id: petdelegate

Text {

id: label

font.pixelSize: 24

text: if (index == 0)

label.text = type + " (default)"

else

text: type

}

}

美化视图

可使用decoration属性来自定义视图的header,footer,section属性.通过向这些属性绑定其他可视对象,就可美化视图.footer中可能包含一个Rectangle元素作为边框,或在header中显示列表的logo图标.

假如一个俱乐部要使用它们的商标颜色来修饰其成员列表.成员列表包含在一个模型中,代理显示模型中的内容.

ListModel {

id: nameModel

ListElement { name: "Alice" }

ListElement { name: "Bob" }

ListElement { name: "Jane" }

ListElement { name: "Harry" }

ListElement { name: "Wendy" }

}

Component {

id: nameDelegate

Text {

text: name;

font.pixelSize: 24

}

}

可以向header和footer属性绑定可视对象来美化这个俱乐部的成员列表.这个可视对象可以直接定义,或在其他文件中定义,或在组件元素中定义..

ListView {

anchors.fill: parent

clip: true

model: nameModel

delegate: nameDelegate

header: bannercomponent

footer: Rectangle {

width: parent.width; height: 30;

gradient: clubcolors

}

highlight: Rectangle {

width: parent.width

color: "lightgray"

}

}

Component { //instantiated when header is processed

id: bannercomponent

Rectangle {

id: banner

width: parent.width; height: 50

gradient: clubcolors

border {color: "#9EDDF2"; width: 2}

Text {

anchors.centerIn: parent

text: "Club Members"

font.pixelSize: 32

}

}

}

Gradient {

id: clubcolors

GradientStop { position: 0.0; color: "#8EE2FE"}

GradientStop { position: 0.66; color: "#7ED2EE"}

}

ListView的小节

ListView 可在sections中包含很多分组,相关的列表项按他们所在的小节进行标记.而且小节还可以指定代理(delegates).

如下列表中包含了人员姓名和所在小组的信息.

ListModel {

id: nameModel

ListElement { name: "Alice"; team: "Crypto" }

ListElement { name: "Bob"; team: "Crypto" }

ListElement { name: "Jane"; team: "QA" }

ListElement { name: "Victor"; team: "QA" }

ListElement { name: "Wendy"; team: "Graphics" }

}

Component {

id: nameDelegate

Text {

text: name;

font.pixelSize: 24

anchors.left: parent.left

anchors.leftMargin: 2

}

}

ListView 元素具有一个叫做section的附加属性(attached property) 可在一个小节内组合临近或相关的元素.section的property属性指明列表元素中的一个属性作为小节名称.criteria属性指明如何显示小节的名称,而delegate与视图的delegate相同.

ListView {

anchors.fill: parent

model: nameModel

delegate: nameDelegate

focus: true

highlight: Rectangle {

color: "lightblue"

width: parent.width

}

section {

property: "team"

criteria: ViewSection.FullString

delegate: Rectangle {

color: "#b0dfb0"

width: parent.width

height: childrenRect.height + 4

Text { anchors.horizontalCenter: parent.horizontalCenter

font.pixelSize: 16

font.bold: true

text: section

}

}

}

}

QML范例详解

研究了一段时间QML,现在对Qt中的一个计算器范例的代码进行分析,并总结一下前面学习的内容.Qt这种语言大多数还是被用于嵌入式设备上,而QML则是专为嵌入式设备而生的.Qt在桌面开发上占据的比例很小,而且已被Nokia出售,未来的前景如何谁也不好说.但Qt确实很棒,祝福一下吧,如果以后Qt支持android和苹果的开发了,在继续深入研究.



上图是运行效果图,界面风格确实很漂亮.鼠标点击按钮后还有一个变灰的反应,整体来说界面简洁大气.而且实现了计算器的基本功能,这里要说明的是,所有功能都是由QML独立完成的,没有任何qt插件参与.而且调整界面的尺寸后,还会使界面发生旋转.这样的功能这样的界面效果要是使用Qt或Delphi,VC来实现的话,相信还是有一点的工作量的.正如前面翻译的文章中所说的那样,QML适合于界面上有大量简单动态元素的情形.像这种计算器程序或时钟程序使用QML实现就太方便了.

在总结一下以前翻译的几篇文章中的要点:QML中的核心是属性绑定,对象的属性发生了变化不一定就一定有函数在给属性赋值.可能是其他的属性与其有绑定关系,当这些属性发生变化时,QML引擎会自动为属性重新计算值.动画效果的实现依靠State和Transition.闲话少说,直接分析代码吧.

计算器程序的组织结构

在core目录中,定义了按钮组件Button和显示计算器输入信息及计算结果的Display组件.core/images目录中是按钮的图片和Display组件的背景图片.



还有一个qmldir文件,这个文件没有后缀.其中存储了目录中组件的名称和位置.

按钮组件

先来看看Button.qml文件的定义.这个文件定义了按钮组件,为了分析方便,我将原码直接拷贝过来,每行代码后面加上注释.

import QtQuick 1.0 //导入QML基本元素 版本号为1.0

BorderImage { //声明一个BorderImage元素 BorderImage一般用来作为边界图像.这里直接用来显示按钮图像

id: button //设置其唯一标识

property alias operation: buttonText.text //定义一个属性别名供外部使用,当给operation赋值或读取operation时,实际上在操作buttonText.text的值 buttonText元素在后面定义

property string color: "" //定义字符串属性color,默认值为""

signal clicked //定义一个信号,这里的信号和Qt中的信号概念上相同,用法上也一致

//source属性指定其图片的地址,注意这里使用了属性绑定,最终的值与color有关,

//如果color的值发生了变化,source的值自动变化.最终计算的source值正好是上图中几个按钮的背景图片的名称

source: "images/button-" + color + ".png"; clip: true

border { left: 10; top: 10; right: 10; bottom: 10 } //设置边界 定义了图像距离外边框的距离 这里上下左右都空闲10个像素

Rectangle { //声明了一个矩形,这个矩形在鼠标点击的时候设置opacity为0.4,使按钮变灰.但不会影响按钮上显示的文字,因为文字是在其下方声明的.

id: shade //设置唯一标示

anchors.fill: button; /*完全平铺到button上*/radius: 10;/*定义圆角半径*/ color: "black"; opacity: 0/*定义了透明度,0为完全透明,1为完全不透明*/

}

Text { //声明按钮上的文本

id: buttonText //设置唯一标识 上面定义属性property alias operation: buttonText.text就引用了这个标识.Text上显示的文本就是text属性的值

anchors.centerIn: parent;/*居中显示*/ anchors.verticalCenterOffset: -1/*垂直居中偏移-1像素*/

font.pixelSize: parent.width > parent.height ? parent.height * .5 : parent.width * .5 //计算字体大小,为按钮宽高最小值的一半

style: Text.Sunken;/*设置文本风格*/ color: "white"; styleColor: "black"; smooth: true

}

MouseArea { //设置鼠标响应区域

id: mouseArea

anchors.fill: parent //整个按钮区域都可响应鼠标

onClicked: {

doOp(operation) //定义doOp函数,注意doOp在calculator.qml中定义,这个qml引用了Button.qml,由于qml是声明式的,因此可先引用后声明(定义).

button.clicked() //触发button的click信号

}

}

states: State { //定义State实现动画效果 这个State实现当mouseArea是鼠标按下状态时,修改shade的属性opacity的值为0.4,也就是当按钮被按下时看到一层淡淡的灰色.

name: "pressed"; when: mouseArea.pressed == true //when关键字定义状态触发的条件

PropertyChanges { target: shade; opacity: .4 } //改变shade的opacity属性

}

}

Display组件

import QtQuick 1.0 //导入1.0版本的QtQuick模块

BorderImage { //定义显示背景图片元素

id: image //唯一标识

property alias text : displayText.text //属性别名 设置text就是给displayText.text赋值

property alias currentOperation : operationText //属性别名 这是一个Text元素类型的属性

source: "images/display.png" //设备背景图片

border { left: 10; top: 10; right: 10; bottom: 10 } //设置图片与边框的距离

Text {

id: displayText

anchors { //定位

right: parent.right;/*右侧与父对象的右侧对齐*/ verticalCenter: parent.verticalCenter;/*垂直居中*/ verticalCenterOffset: -1/*垂直偏移量-1 显示稍偏下*/

rightMargin: 6; /*右边界间隔6个像素*/left: operationText.right/*左侧与operationText的右侧对齐*/

}

font.pixelSize: parent.height * .6; text: "0"; horizontalAlignment: Text.AlignRight; elide: Text.ElideRight

color: "#343434"; smooth: true; font.bold: true

}

Text {

id: operationText

font.bold: true;/*粗体*/ font.pixelSize: parent.height * .7

color: "#343434"; smooth: true

anchors { left: parent.left;/*靠左显示*/ leftMargin: 6;/*左侧边距6像素*/ verticalCenterOffset: -3; verticalCenter: parent.verticalCenter }

}

}

Display组件定义了一个背景图,上面有两个Text,这两个Text一个靠左,一个靠右,平铺在Display组件上,而且两个Text直接具有描点关系:anchors{displayText.left: operationText.right}.displayText的左侧总与operationText的右侧相连.说明在改变大小时operationText不变,而displayText是可伸展的.

calculator定义

两个共用组件介绍完了,现在看看calculator.qml.这是计时器的定义文件.

import QtQuick 1.0

import "Core" //导入Core目录中定义的组件 引擎查找目录中的qmldir文件(无后缀),根据其中的内容导入定义的组件.

import "Core/calculator.js" as CalcEngine //导入javaScript文件内容 也可作为一个组件来看,并定义了组件别名,下面使用文件中定义的函数时可用:别名.方法名

Rectangle {

id: window

width: 360; height: 480 //定义窗口尺寸

color: "#282828"

property string rotateLeft: "\u2939"

property string rotateRight: "\u2935"

property string leftArrow: "\u2190"

property string division : "\u00f7"

property string multiplication : "\u00d7"

property string squareRoot : "\u221a"

property string plusminus : "\u00b1"

function doOp(operation) { CalcEngine.doOperation(operation) } //定义了个函数,供下面调用.这个函数又调用了js文件中的doOperation函数,注意参数operation是按钮上的文字内容.

Item {

id: main

state: "orientation " + runtime.orientation //runtime.orienttation返回界面的显示方向. 如果方向改变,就会重新设置state的值,其属性也会按state定义的相应更改.

property bool landscapeWindow: window.width > window.height

property real baseWidth: landscapeWindow ? window.height : window.width //取宽高中最小的那个值

property real baseHeight: landscapeWindow ? window.width : window.height //取宽高中最大的那个值

property real rotationDelta: landscapeWindow ? -90 : 0

rotation: rotationDelta //根据窗口宽与高的大小来调整旋转角度,只用一行代码搞定界面旋转

width: main.baseWidth

height: main.baseHeight

anchors.centerIn: parent

//定义一个Column元素,单列排布其中的子元素.上面是Display 下面是多个按钮的区域

Column {

id: box; spacing: 8

anchors { fill: parent; topMargin: 6; bottomMargin: 6; leftMargin: 6; rightMargin: 6 }

//显示Display组件

Display {

id: display

width: box.width-3

height: 64

}

//定义按钮区域 应使用Column元素声明 其中的子元素垂直分布 共分三个区域按钮 界面中紫色,绿色,及下面的其他按钮三个部分

Column {

id: column; spacing: 6

property real h: ((box.height - 72) / 6) - ((spacing * (6 - 1)) / 6)//计算出每个按钮的高度

property real w: (box.width / 4) - ((spacing * (4 - 1)) / 4) //计算出每个按钮的宽度

//定义紫色按钮区域 按钮之所以显示为紫色,因为Button的color属性设置为purple,在Button按钮组件定义中,其背景图片的source属性与color绑定,确定了显示哪个图片

Row { //Row元素定义一行,其中包含的元素水平布局

spacing: 6

Button { width: column.w; height: column.h; color: 'purple'; operation: "Off" }

Button { width: column.w; height: column.h; color: 'purple'; operation: leftArrow }

Button { width: column.w; height: column.h; color: 'purple'; operation: "C" }

Button { width: column.w; height: column.h; color: 'purple'; operation: "AC" }

}

//定义绿色按钮区域

Row {

spacing: 6

property real w: (box.width / 4) - ((spacing * (4 - 1)) / 4)

Button { width: column.w; height: column.h; color: 'green'; operation: "mc" }

Button { width: column.w; height: column.h; color: 'green'; operation: "m+" }

Button { width: column.w; height: column.h; color: 'green'; operation: "m-" }

Button { width: column.w; height: column.h; color: 'green'; operation: "mr" }

}

//定义其他按钮

Grid { //Grid元素定义一个网格,其中的元素都占据一个小格

id: grid; rows: 5;/*指定网格的行数*/ columns: 5;/*指定网格的列数*/ spacing: 6

property real w: (box.width / columns) - ((spacing * (columns - 1)) / columns)

Button { width: grid.w; height: column.h; operation: "7"; color: 'blue' }

Button { width: grid.w; height: column.h; operation: "8"; color: 'blue' }

Button { width: grid.w; height: column.h; operation: "9"; color: 'blue' }

Button { width: grid.w; height: column.h; operation: division }

Button { width: grid.w; height: column.h; operation: squareRoot }

Button { width: grid.w; height: column.h; operation: "4"; color: 'blue' }

Button { width: grid.w; height: column.h; operation: "5"; color: 'blue' }

Button { width: grid.w; height: column.h; operation: "6"; color: 'blue' }

Button { width: grid.w; height: column.h; operation: multiplication }

Button { width: grid.w; height: column.h; operation: "x^2" }

Button { width: grid.w; height: column.h; operation: "1"; color: 'blue' }

Button { width: grid.w; height: column.h; operation: "2"; color: 'blue' }

Button { width: grid.w; height: column.h; operation: "3"; color: 'blue' }

Button { width: grid.w; height: column.h; operation: "-" }

Button { width: grid.w; height: column.h; operation: "1/x" }

Button { width: grid.w; height: column.h; operation: "0"; color: 'blue' }

Button { width: grid.w; height: column.h; operation: "." }

Button { width: grid.w; height: column.h; operation: plusminus }

Button { width: grid.w; height: column.h; operation: "+" }

Button { width: grid.w; height: column.h; operation: "="; color: 'red' }

}

}

}

//定义状态,main元素的state属性指定为如下状态名称时,其属性值就会发生改变 通常为了具有动画效果,states要与transitions配合使用

states: [

State {

name: "orientation " + Orientation.Landscape

PropertyChanges { target: main; rotation: 90 + rotationDelta; width: main.baseHeight; height: main.baseWidth }

},

State {

name: "orientation " + Orientation.PortraitInverted

PropertyChanges { target: main; rotation: 180 + rotationDelta; }

},

State {

name: "orientation " + Orientation.LandscapeInverted

PropertyChanges { target: main; rotation: 270 + rotationDelta; width: main.baseHeight; height: main.baseWidth }

}

]

//定义动画效果

transitions: Transition {

SequentialAnimation { //定义一个顺序执行的动画

RotationAnimation { direction: RotationAnimation.Shortest; duration: 300; easing.type: Easing.InOutQuint } //旋转动画效果属性

NumberAnimation { properties: "x,y,width,height"; duration: 300; easing.type: Easing.InOutQuint } //在x,y,width,height属性发生变化时的动画属性

}

}

}

}

算法

计时器的算法定义在一个单独的JavaScript文件中.

var curVal = 0

var memory = 0

var lastOp = ""

var timer = 0

function disabled(op) {

if (op == "." && display.text.toString().search(/\./) != -1) {

return true

} else if (op == squareRoot && display.text.toString().search(/-/) != -1) {

return true

} else {

return false

}

}

function doOperation(op) {

if (disabled(op)) {

return

}

if (op.toString().length==1 && ((op >= "0" && op <= "9") || op==".") ) {

if (display.text.toString().length >= 14)

return; // No arbitrary length numbers

if (lastOp.toString().length == 1 && ((lastOp >= "0" && lastOp <= "9") || lastOp == ".") ) {

display.text = display.text + op.toString()

} else {

display.text = op

}

lastOp = op

return

}

lastOp = op

if (display.currentOperation.text == "+") { //已经按下了+号

display.text = Number(display.text.valueOf()) + Number(curVal.valueOf())

} else if (display.currentOperation.text == "-") {

display.text = Number(curVal) - Number(display.text.valueOf())

} else if (display.currentOperation.text == multiplication) {

display.text = Number(curVal) * Number(display.text.valueOf())

} else if (display.currentOperation.text == division) {

display.text = Number(Number(curVal) / Number(display.text.valueOf())).toString()//开始计算

} else if (display.currentOperation.text == "=") {

}

if (op == "+" || op == "-" || op == multiplication || op == division) {

display.currentOperation.text = op

curVal = display.text.valueOf()

return

}

curVal = 0

display.currentOperation.text = ""

if (op == "1/x") {

display.text = (1 / display.text.valueOf()).toString()

} else if (op == "x^2") {

display.text = (display.text.valueOf() * display.text.valueOf()).toString()

} else if (op == "Abs") {

display.text = (Math.abs(display.text.valueOf())).toString()

} else if (op == "Int") {

display.text = (Math.floor(display.text.valueOf())).toString()

} else if (op == plusminus) {

display.text = (display.text.valueOf() * -1).toString()

} else if (op == squareRoot) {

display.text = (Math.sqrt(display.text.valueOf())).toString()

} else if (op == "mc") {

memory = 0;

} else if (op == "m+") {

memory += display.text.valueOf()

} else if (op == "mr") {

display.text = memory.toString()

} else if (op == "m-") {

memory = display.text.valueOf()

} else if (op == leftArrow) {

display.text = display.text.toString().slice(0, -1)

if (display.text.length == 0) {

display.text = "0"

}

} else if (op == "Off") {

Qt.quit();

} else if (op == "C") {

display.text = "0"

} else if (op == "AC") {

curVal = 0

memory = 0

lastOp = ""

display.text ="0"

}

}

QML负责GUI,C++负责业务逻辑的范例

在declarative目录中,有个minehunt范例,实现了在C++中加载QML界面,并用C++来处理QML界面上的鼠标动作.这种思路和传统的GUI相似,感觉比较顺畅.否则运行一个QML,还要使用qmlviewer,上面带一大堆菜单按钮,看着够别扭的.

在main函数中,创建了一个QDeclarativeView实例,这个实例负责显示QML界面.接着创建负责处理业务逻辑的MinehuntGame实例,并在view加载QML文件后,将其设置为引擎的上下文对象.这样就可以直接在QML中使用MinehuntGame类中的属性和方法了.感觉设置上下文后,将上下文类实例与QML界面做了融合,QML中的鼠标点击等事件就可以调用类中方法进行处理,并可以绑定到实例的属性.

#include <QtGui/QApplication>

#include <QtDeclarative/QDeclarativeView>

#include <QtDeclarative/QDeclarativeContext>

#include <QtDeclarative/QDeclarativeEngine>

#include "minehunt.h"

int main(int argc, char *argv[])

{

QApplication app(argc, argv);

QDeclarativeView canvas;

qmlRegisterType<TileData>();//注册TileData类,这个类不需要在QML中实例化.

MinehuntGame* game = new MinehuntGame();

canvas.engine()->rootContext()->setContextObject(game);

canvas.setSource(QString("qrc:minehunt.qml"));

QObject::connect(canvas.engine(), SIGNAL(quit()), &app, SLOT(quit())); //QML退出,应用程序也退出

canvas.setGeometry(QRect(100, 100, 450, 450));

canvas.show();

return app.exec();

}

在程序中定义了代表扫雷界面中每个方块的TileData类,用来记录这个方块的状态,如做标记的,被点开的,或有雷的.那么类的实例是如何与QML进行关联同步的呢?在MinehuntGame中定义了一个属性QDeclarativeListProperty<TileData> tiles(),与_tiles成员绑定,并在类构造时向tiles中填充多个TileData实例.在QML中可以看到如下声明:

Grid {

anchors.horizontalCenter: parent.horizontalCenter

columns: 9; spacing: 1

Repeater {

id: repeater

model: tiles

delegate: Tile {}

}

}

这样就按MinehuntGame实例中的tiles属性进行构造Grid中的元素了,每个TileData实例生成一个Tile组件.在操作Tile组件时又会反过来调用MinehuntGame中的方法.先看看那个表情图标,点击时会重置界面.

Image {

anchors.bottom: field.bottom; anchors.bottomMargin: 15

anchors.right: field.right; anchors.rightMargin: 20

source: isPlaying ? 'MinehuntCore/pics/face-smile.png' ://isPlaying,hasWon是MinehuntGame实例中的属性,这里做了绑定,如果实例中的属性发生变化,则图标自动变化

hasWon ? 'MinehuntCore/pics/face-smile-big.png': 'MinehuntCore/pics/face-sad.png'

MouseArea { anchors.fill: parent; onPressed: reset() }//点击的时候调用MinehuntGame实例的reset方法,重置雷区,上面也有个关于雷区的绑定,重置后界面自动更新

}

在看看点击雷区的一个方块时的动作触发过程.这里有两个成员对象,index和modelData,都是和模型绑定有关的,index表示当前选中的模型元素索引,modelData表示当前选中子模型对应的元素.在QML中可通过modelData访问TileData元素属性.下面的声明中指定,鼠标左键点击调用MinehuntGame的flip方法,右击调用flag方法.在这两个C++函数中判断是否触雷等逻辑,并修改类实例的状态,通过绑定,界面元素自动更新.

MouseArea {

anchors.fill: parent

acceptedButtons: Qt.LeftButton | Qt.RightButton

onClicked: {

field.clickx = flipable.x//鼠标点击时设置主界面的clickx,clicky属性

field.clicky = flipable.y

var row = Math.floor(index / 9)

var col = index - (Math.floor(index / 9) * 9)

if (mouse.button == undefined || mouse.button == Qt.RightButton) {

flag(row, col)

} else {

flip(row, col)

}

}

onPressAndHold: {

field.clickx = flipable.x

field.clicky = flipable.y

var row = Math.floor(index / 9)

var col = index - (Math.floor(index / 9) * 9)

flag(row, col)

}

}

那么触雷后的例子系统是如何声明的呢?

transitions: Transition {

SequentialAnimation {

ScriptAction {

script: {

var ret = Math.abs(flipable.x - field.clickx)

+ Math.abs(flipable.y - field.clicky);

if (modelData.hasMine && modelData.flipped)

pauseDur = ret * 3

else

pauseDur = ret

}

}

PauseAnimation {

duration: pauseDur

}

RotationAnimation { easing.type: Easing.InOutQuad }

ScriptAction { script: if (modelData.hasMine && modelData.flipped) { expl.explode = true } }//这里指定如果modelData即TileData数据对象属性hasMine或flipped为true,则触发例子效果.

}

}

expl的定义:Explosion { id: expl }.

Explosion的定义:

import QtQuick 1.0

import Qt.labs.particles 1.0

Item {

property bool explode : false

Particles {//定义了一个粒子对象

id: particles

width: 40

height: 40

lifeSpan: 1000

lifeSpanDeviation: 0

source: "pics/star.png"//粒子图像

count: 0

angle: 270

angleDeviation: 360

velocity: 100

velocityDeviation: 20

z: 100

}

states: State { name: "exploding"; when: explode

StateChangeScript {script: particles.burst(200); }

}

}

总结:要向将QML和C++结合起来,实现传统的开发方式,核心的代码就是canvas.engine()->rootContext()->setContextObject(game);这样就可以在QML中调用game实例的属性和函数了.

**************测试如何在C++中获取TextInput元素中输入的内容**************

根据上面的范例分析可以在QML中访问C++中定义的函数或属性,那么C++中怎么获取到QML中的界面内容呢?网上说有三种方法,一种是在C++的插件获取,一种是在C++中解析QML中的元素,最后一种是在C++中定义带参数的槽函数,在QML中的事件中调用这个槽,并将TextInput中的内容作为参数传到C++中.感觉最后一种方法最方便了,因此做了如下测试:

定义QML文件:

import QtQuick 1.0

Rectangle{

id: window

width: 240; height: 250

BorderImage{

y:0

id:button

source:"pics/back.png"

MouseArea{

id:mouseArea

anchors.fill:parent

onClicked:buttonClicked(textInput.text)//按钮点击的时候调用C++中的buttonClicked槽函数

}

Rectangle{

id:shade

anchors.fill:button; radius: 10; color: "black"; opacity: 0

}

states:State{

name:"pressed";when: mouseArea.pressed == true

PropertyChanges{ target: shade; opacity: 0.4}

}

}

BorderImage

{

source: "pics/back.png";width:82;height:22;y:50

TextInput{

id:textInput

text: qsTr("text")

y:1;x:1;width:80;height:20;

}

}

}

C++类:

头文件

#ifndef QMLCONTROL_H

#define QMLCONTROL_H

#include <QObject>

class QmlControl : public QObject

{

Q_OBJECT//必须加上Q_OBJECT宏,否则在QML中无法触发buttonClicked函数

public:

QmlControl(void);

~QmlControl(void);

public slots:

Q_INVOKABLE void buttonClicked(QString textInput);

};

#endif

cpp文件

#include "QmlControl.h"

#include <QMessageBox>

QmlControl::QmlControl(void)

{

setObjectName("mainObject");

}

QmlControl::~QmlControl(void)

{

}

void QmlControl::buttonClicked(QString textInput)

{

QMessageBox::information(NULL, "", textInput);

}

界面上放一个QDeclarativeView控件,并在构造函数中加载QML,设置引擎中的对象:

GetQMLInputTextValue::GetQMLInputTextValue(QWidget *parent, Qt::WFlags flags)

: QMainWindow(parent, flags)

{

ui.setupUi(this);

qmlRegisterType<QmlControl>();

QmlControl *ctrl = new QmlControl();

ui.declarativeView->engine()->rootContext()->setContextObject(ctrl);

ui.declarativeView->setSource(QString("qrc:/Resources/test.qml"));

connect(ui.declarativeView->engine(),SIGNAL(quit()), qApp, SLOT(quit()));

}

注意我将QML和其中的图片放入资源中,这里引用QML的时候需要以qrc开头,否则访问不到图片文件.



QT程序员使用QML

使用QML并不需要Qt的知识,如果你已经熟悉Qt,那么很多知识都可以直接用于学习和使用QML.当然,使用QML定义UI的应用程序还是需要使用Qt实现非UI逻辑的.

熟悉的概念

QML直接支持如下Qt中的概念:

QAction -action 类型

QObject 信号槽 - 可用于调用JavaScript函数

QObject 属性- 在JavaScript中当做变量使用

QWidget - QDeclarativeView 是一个QML显示部件

Qt 模型 - 可直接用在数据绑定中(QAbstractItemModel)

Extending QML Functionalities using C++以及Integrating QML Code with existing Qt UI code中需要Qt知识

QML项与QWidget比较

QML中的item与QWidget很相似:他们都定义了用户界面的外观.(注意通常QWidget并不是用来定义视图代理外观的,QML项也可这样使用.)

有三不同种结构的QWidget:

不能作为父部件的简单部件(QLabel, QCheckBox, QToolButton等)

常作为其他部件的父部件(QGroupBox, QStackedWidget, QTabWidget等)

由子部件组成的组合部件(QComboBox, QSpinBox, QFileDialog, QTabWidget等)

QML项也可这样分类.分类如下.

简单部件

最主要的原则是要记住当在C++中继承一个新的QDeclarativeItem类时不要定义任何的外观策略--留到QML使用元素时再定义.

作为范例,假设你要重用按钮项目.因此需要定义一个QDeclarativeItem子类实现按钮功能,与QToolButton继承于QWidget 一样,按上面的原则, QDeclarativeButton 类不应该有任何与外观相关的代码--只需要处理使能,触发等操作.

但这些已经被Qt中的QAction实现了.

QAction是UI无关的,可绑定到QPushButton, QCheckBox, QMenu,QToolButton,以及其他可视部件.

因此QML中以及具有了复选框功能--利用QAction.仅在QML中定义--按钮外观,状态的过度,如何精确的响应鼠标,键盘,或触摸输入.

为说明这点,请注意QDeclarativeTextEdit构建于QTextControl, QDeclarativeWebView构建于QWebPage,ListView 构建于QAbstractItemModel,QTextEdit, QWebView,和 QListView构建于UI无关的组件.

独立封装外观对QWidget是很重要的,QML中的组件概念也保留了这个观点.如果生成一个完整的应用程序,需要由一致的外观风格,需要创建一系列可重用的具有期望外观的组件.

为实现这个可重用按钮,需要简单的创建一个QML组件.

父部件

父部件提供了通用方法访问任意的子部件.QTabWidget 提供可访问多个页面(pages)的接口,同时只有一个page被显示,以及切换page的机制(QTabBar).QScrollArea 具有位于部件边缘的滚动条,可在有限的空间内浏览超大部件.

这些组件几乎都可以在QML中直接创建.只有几个对象需要特殊的事件处理,如Flickable,需要在C++中实现.

例如,假设要创建可大量用于应用程序中的一般的标签部件(tab widget),根据数据量判断是否需要分页显示.

QML组件和QWidget的parent概念最明显区别在于,子项位置是相对于父项的,但不会要求子项完全包含在父项中(当然可在必要时设置子项的clipped属性).这个差异具有深远的影响,例如:

围绕部件的阴影或高亮可作为部件的子项.

粒子效果可以漂移到其发起的对象之外.

过度动画可以将项目移动到屏幕范围之外隐藏他们.

组合部件

一些部件支持组合其他部件作为其实现细节,并为组合体提供高层次的API.例如QSpinBox 由一个QLineEdit和操作数值的向上向下按钮组成的.QFileDialog 作为一个完整的部件为用户提供查找和选择文件名称的功能.

开发可重用QML时,通常都是这样做的,使用已定义的item组合出新的item.

唯一需要注意的是,要考虑到使用组合体的用户可能希望采用动画和过度.例如,一个spinbox可能需要平滑过度到任意值,因此这个spinbox项需要由足够灵活,以允许这样的动画.

QML项与QGraphicsWidget比较

QML项和QGraphicWidget的主要不同点是使用方式.技术实现大致相同的,但实际上QML元素是可声明和可组合的,而QGraphicWidget是一个基本元素,用于协调QGraphicScene和部件.QML项和QGraphicWidget都从QGraphicsObject继承,可以共存.在布局系统中和与其他组件交互上是不同的.注意QGraphicWidget更倾向于要求在一个包中定义,而与QGraphicWidget等价的QML项可能由跨多个QML文件的QML项组合而成,但还是可以加载到C++的单个QGraphicsObject
对象中.

QGraphicsWidget通常使用QGraphicLayout来布局.QML不使用QGraphicLayout,因为Qt的布局对动画和UI的流畅性不太友好,因此几何上的接口是主要的不同点.当定义QML元素时,允许设计者使用绝对几何位置,绑定或描点(从QDeclarativeItem继承而来)定位其外边框,而不是使用布局或指定尺寸.如果适合指定尺寸就将其放置在QML文档中,让设计者知道如何更好的使用这个元素,但仍可完全控制界面外观.

其他主要不同在于QGraphicWidget用于布局模型,其具有独立的UI和逻辑.相反,QML实体通常是具有单一目标的项,不会在所有者中履行用户用例,而是在QML文件中组成等价的部件,要避免在项定义中涉及UI逻辑和组成可视化元素.而是尝试定义更加通用的实体,以便于在QML中定义界面外观(包括UI逻辑).

这两点不同决定了不同的交互方式. QGraphicsWidget是 QGraphicsObject的子类,用于在C++中轻松定义流畅的UI界面,而 QDeclarativeItem 是 QGraphicsObject 的子类用于在QML中定义流畅的UI界面.因此主要的不同是其暴露的接口,及设计时与其交互的对象(为QML声明实体,QGraphicWidget则不用如此,因为你需要在子类中定义UI逻辑)

如果希望同时使用QML和C++定义UI,例如要进行过度,推荐使用 QDeclarativeItem子类(也可同时使用QGraphicWidget).允许在C++中轻松的为每个C++组件创建一个根项 LayoutItem,向场景中加载独立的QML(可能定义在不同文件中,组成独立的UI和逻辑)代表的部件,替代个别的QGraphicWidget.

使用C++扩展QML功能



使用C++扩展QML功能

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

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

添加类型

import People 1.0

Person {

name: "Bob Jones"

shoeSize: 12

}

上面的QML片段定义了一个Person实例及其name和shoeSize属性.本质上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

· QString, QUrl, QColor

· QDate, QTime, QDateTime

· QPoint, QPointF, QSize, QSizeF, QRect, QRectF

· 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类型对象赋给BirthdayParty的host属性,将三个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++的继承层次,并可自由的在类型间进行强制转换.可向声明为基类类型的对象或对象列表属性中赋予子类的实例.在这个片段中,guests和host都是Person类型的(属性或属性列表),但可赋值为从Person继承的Boy和Girl对象的实例.

要给属性赋值,属性的类型必须在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 范例中是生成Boy和Girl类型的完整代码.

默认属性

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

}

}

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

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

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

Q_PROPERTY(ShoeDescription *shoe READ shoe)

类ShoeDescription定义了分组属性块中的属性,本例中为size,color,brand和price属性.

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

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).附加对象中的属性可在附加属性块中访问.

任何声明了public的qmlAttachedProperties() 函数的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,有两个属性a和b.属性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";

}

};

QML代码与现有Qt UI代码整合



有很多方式可将QML整合到基于QWidget UI应用程序中,具体方案依赖于UI代码的特征.

与基于QWidget的UI整合

如果有一个基于QWidge的UI界面,可使用QDeclarativeView整合QML部件.QDeclarativeView继承于QWidget,因此可像其他QWidget部件一样添加到用户界面中. 使用 QDeclarativeView::setSource()向视图中加载QML文件,并将视图添加到UI中:

QDeclarativeView *qmlView = new QDeclarativeView;

qmlView->setSource(QUrl::fromLocalFile("myqml.qml"));

QWidget *widget = myExistingWidget();

QVBoxLayout *layout = new QVBoxLayout(widget);

layout->addWidget(qmlView);

这种方式的缺点是 QDeclarativeView初始化速度慢,而且比QWidget占用更多内存,创建大量的 QDeclarativeView会使性能下降.这时最好在QML中重写部件,并使用主QML部件加载这些部件,替换掉 QDeclarativeView.

要知道QWidget相对于QML是不同的用户界面类型,因此有时将基于QWidget的应用程序转化为QML是不妥的.QWidget应该用于UI中有大量复杂且静止元素的情形,而QML则用于UI中有大量简单动态元素的情形.

与基于QGraphicsView的UI程序整合

向QGraphicScene中添加QML部件

如果有一个基于图形视图框架(Graphics View Framework)的UI程序,可直接将QML部件整合到 QGraphicsScene.使用QDeclarativeComponent 从QML文件创建一个 QGraphicsObject,再使用QGraphicsScene::addItem()将这个图像对象添加到场景,或替换场景中已存的项目.

例如:

QGraphicsScene* scene = myExistingGraphicsScene();

QDeclarativeEngine *engine = new QDeclarativeEngine;

QDeclarativeComponent component(engine, QUrl::fromLocalFile("myqml.qml"));

QGraphicsObject *object =

qobject_cast<QGraphicsObject *>(component.create());

scene->addItem(object);

如下QGraphicView选项用来优化QML界面效率:

QGraphicsView::setOptimizationFlags(QGraphicsView::DontSavePainterState)

QGraphicsView::setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate)

QGraphicsScene::setItemIndexMethod(QGraphicsScene::NoIndex)

在QML中加载QGraphicsWidget对象

另一种方式是在QML中应用已存的QGraphicWidget对象,现在需要构建一个基于QML的场景.在范例graphics layouts example中展示如何在QML中加载Qt图像布局类型,以便于在配合QGraphicsLinearLayout 和QGraphicsGridLayout使用QGraphicsWidget .

QML编程入门



欢迎来到声明式UI语言QML的世界.在本入门教程中,我们使用QML创建一个简单的文本编辑器.阅读这个教程后,就可以使用QML和Qt C++开发应用程序了.

安装

首先需要安装包含Qt Quick的Qt最新版本,现在是Qt4.7.安装教程包括安装说明书和不同平台的需求.

Qt Quick包含一个叫做QML的声明式语言,Qt Declarative Module,和 QML Viewer.

QML构造用户界面

我们要构造的应用程序是一个简单的文本编辑器,可以加载,保存,以及执行一些文本处理.本教程包括两个部分.第一个部分使用QML设计应用程序布局和行为.第二个部分中使用Qt C++实现加载和保存文本.应用Qt元对象系统(Qt's Meta-Object System)可以将C++中的函数导入作为QML元素的属性进行访问.利用QML和Qt C++,可高效的将界面逻辑与应用程序逻辑解耦.



最终代码见 examples/tutorials/gettingStarted/gsQml目录.首先需要在examples/tutorials/gettingStarted/gsQml/编译C++插件.将C++插件生成到QML文件可访问的目录中.

要启动文本编辑器,仅需要使用qmlviewer工具,并包含一个QML文件名称为参数.本教程的C++部分假设读者了解基本的Qt编译过程.

教程章节:

1. 定义按钮和菜单Defining a Button and a Menu

2. 实现菜单栏Implementing a Menu Bar

3. 创建文本编辑器Building a Text Editor

4. 美化文本编辑器Decorating the Text Editor

5. 使用Qt C++扩展QMLExtending QML using Qt C++

定义按钮和菜单

基本组件—按钮

我们构建一个按钮作为文本编辑器程序的开始.功能上,按钮具有鼠标敏感区域和一个标签(label).用户点击按钮后执行一个动作.

在QML中,基本的可视项是Rectangle 元素. Rectangle 元素拥有控制外观和位置的属性.

import QtQuick 1.0

Rectangle {

id: simplebutton

color: "grey"

width: 150; height: 75

Text{

id: buttonLabel

anchors.centerIn: parent

text: "button label"

}

}

首先, import QtQuick 1.0使qmlviewer工具导入我们稍后需要的QML元素.这行代码在每个QML文件中都是必须的.注意导入语句中包含Qt模块的版本号.

这个矩形包含一个唯一标识simplebutton,绑定到id属性上. Rectangle 元素设置属性值的方式为:属性名称,后跟冒号,而后是值.本例中,颜色grey赋给了矩形的color属性.同样设置了矩形的width和height属性.

Text元素为不可编辑的文本框.将Text元素命名为buttonLabel.要给Text元素设置字符串内容需要给其text属性赋值.标签包含在Rectangle中,为了让其居中,设置Text元素的相对于父元素(simplebutton)的描点属性.为了让布局更加简单,描点可与其他项的描点绑定.

将上面的代码保存为SimpleButton.qml. 使用这个文件名做参数启动qmlviewer将看到带有文本标签的灰色矩形.



为了实现按钮的点击功能,我们可以处理QML的事件.QML事件与Qt的信号槽机制类似.触发信号时会调用与其连接的槽.

Rectangle{

id:simplebutton

...

MouseArea{

id: buttonMouseArea

anchors.fill: parent //在矩形区域内描定Mouse Area的所有边

//onClicked处理按钮点击事件

onClicked: console.log(buttonLabel.text + " clicked" )

}

}

在simplebutton中包含一个MouseArea元素.MouseArea元素描述一个可检测鼠标移动的交互区域.在按钮中我们将MouseArea完全平铺到其父对象simplebutton上.anchors.fill语法用来访问叫做anchors的组合属性中的fill属性.QMl使用基于描点的布局(anchor-based layouts)可将项描定到其他项上,创建出强健的布局.

当鼠标在MouseArea区域内移动时会触发很多信号.其中当用户点击被许可的鼠标按钮(默认是左按钮)时会调用onClicked信号.可以设置onClicked的处理事件.本例中,当在MouseArea中点击鼠标时会调用console.log()输出文本.这个函数可用于在调试时输出文本信息.

SimpleButton.qml中的代码实现在屏幕上显示一个按钮,并在鼠标点击时输出文本.

Rectangle {

id: button

...

property color buttonColor: "lightblue"

property color onHoverColor: "gold"

property color borderColor: "white"

signal buttonClick()

onButtonClick: {

console.log(buttonLabel.text + " clicked" )

}

MouseArea{

onClicked: buttonClick()

hoverEnabled: true

onEntered: parent.border.color = onHoverColor

onExited: parent.border.color = borderColor

}

//determines the color of the button by using the conditional operator

color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor

}

完整功能的按钮代码在Button.qml中.上述的代码片段有些被省略,因为有些已经在上节中介绍过或与当前讨论无关.

使用带有属性类型名的语法来自定义属性.代码中,buttonColor属性,是color类型的,声明并赋值为"lightblue".buttonColor稍后用在确定按钮填充颜色的条件操作中.注意属性赋值可能使用等号(=)操作符,而属性绑定使用冒号(:)操作符.自定义属性使内部项可与外部交互.QML基本类型(QML types)包括int,string,real,以及variant类型.

绑定onEntered和onExisted信号处理按钮边框颜色,鼠标悬停在按钮上时为黄色,鼠标移出时恢复颜色.

Button.qml中定义了一个buttonClick()信号,将signal关键字放在信号名称前面.所有信号的事件处理器会被自动创建,名称前以on做前缀.例如,onButtonClick是buttonClick的处理器.onButtonClick被赋予一个可执行的动作.在这个按钮范例中,onClick按钮事件中调用了onButtonClick,简单的输出一行文本.onButtonClick信号使外部对象可处理按钮的鼠标区域事件.例如,如果项中含有多个MouseArea声明,buttonClick信号可以更好的区分多个MouseArea的信号处理代码.

现在我们了解了如何定义一个可处理鼠标移动的QML元素.在Rectangle中定义了一个文本标签,自定义其属性,处理鼠标的移动.在元素内部创建子元素的概念会贯穿整个文本编辑器应用程序.

按钮必须作为组件来执行动作才有使用价值.下节中将创建一个包含这种按钮的菜单.



创建菜单页

上节中阐述了如何创建元素并在单独的QML文件中设置行为.本节将说明如何导入QML元素,如何重用已有组件构建其他组件.

菜单显示一列内容,其中的每个项都可以执行一个动作.在QML中,有很多种方式创建菜单.首先,我们创建包含可执行不同动作按钮的菜单.菜单代码在FileMenu.qml中.

import QtQuick 1.0 \\import the main Qt QML module

import "folderName" \\import the contents of the folder

import "script.js" as Script \\import a Javascript file and name it as Script

上述语法展示如何使用import关键字.这里需要使用不在同一目录中的JavaScript文件或QML文件.由于Button.qml与FileMenu.qml在同一目录中,不必导入Button.qml就可直接使用.可直接使用Button{}声明一个按钮元素,与Rectangle{}的声明一样.

FileMenu.qml:

Row{

anchors.centerIn: parent

spacing: parent.width/6

Button{

id: loadButton

buttonColor: "lightgrey"

label: "Load"

}

Button{

buttonColor: "grey"

id: saveButton

label: "Save"

}

Button{

id: exitButton

label: "Exit"

buttonColor: "darkgrey"

onButtonClick: Qt.quit()

}

}

在FileMenu.qml中,声明了三个按钮元素.他们都在一个Row元素中声明的,这是一个定位器,将其子元素按行定位.Button声明在Button.qml中,与上节定义的Button.qml一致.新创建的按钮可设置属性绑定,在exitButton上增加了onButtonClick处理函数,由Button.qml中定义的onButtonClick来触发调用.



Row定义在Rectangle中,创建了包含一行按钮的矩形容器.这个额外的矩形采用间接的方式在菜单中组织了一行按钮.

这个阶段定义的编辑菜单非常简单.菜单按钮具有的标签为:Copy,Paste,Select All.



结合前面介绍的知识和定义的组件,我们已经可以组合这些菜单页生成一个菜单栏了,包括选择菜单的按钮,下面看看如何在QML中组织这些数据.

实现菜单栏

我们的文本编辑器程序需要使用菜单栏显示菜单.菜单栏可以切换不同的菜单,用户可选择显示哪个菜单.菜单间的切换比仅仅显示在一行中需要更多组织信息.QML使用模型和视图来组织数据和显示数据.

使用数据模型和视图

QML有用来显示数据模型(data models)的不同的数据视图(data views ).我们的菜单栏将显示列表中的菜单,头部显示一行菜单名称.菜单列表定义在VisualItemModel中.VisualItemModel元素中包含需要显示的元素,如Rectangle元素和导入的UI元素.其他模型类型如 ListModel 元素则需要显示其数据的代理(视图).

我们在menuListModel中定义两个可视化项,一个FileMenu一个EditMenu.并自定义这两个菜单,显示在ListView中.MenuBar.qml文件包含了简单的QML定义,编辑菜单定义在EditMenu.qml中.

VisualItemModel{

id: menuListModel

FileMenu{

width: menuListView.width

height: menuBar.height

color: fileColor

}

EditMenu{

color: editColor

width: menuListView.width

height: menuBar.height

}

}

ListView元素用来显示其代理模型.代理声明可在Row元素中显示模型元素,也可在Grid中显示模型元素.menuListModel已经定义需要显示的项了,因此,我们不再需要声明模型了.

ListView{

id: menuListView

//Anchors are set to react to window anchors

anchors.fill:parent

anchors.bottom: parent.bottom

width:parent.width

height: parent.height

//the model contains the data

model: menuListModel

//控制菜单开关的移动

snapMode: ListView.SnapOneItem

orientation: ListView.Horizontal

boundsBehavior: Flickable.StopAtBounds

flickDeceleration: 5000

highlightFollowsCurrentItem: true

highlightMoveDuration:240

highlightRangeMode: ListView.StrictlyEnforceRange

}

另外,ListView从Flickable继承,使列表可响应鼠标拖拽和手势.上述代码的最后部分设置flick属性可在视图中创建出期望的flick移动效果.特别是highlightMoveDuration属性设置flick的延续时间.较高的highlightMoveDuration值使菜单切换变慢.

ListView通过索引维护模型项目,模型中的可视项都是通过索引进行访问的,索引顺序与声明顺序相同.修改当前索引值会改变ListView中的高亮项.菜单栏的头部分证实了这个效果.这里一行中有两个按钮,点击后会改变当前菜单.点击fileButton将当前菜单切换为file菜单,由于FileMenu在menuListModel中第一个声明,因此其索引为0.同样,点击editButton将当前菜单切换为EditMenu.

labelList矩形的z属性值为1,强调其显示在菜单栏的最前面.项的z属性值越高越靠前显示.z属性值默认为0.

Rectangle{

id: labelList

...

z: 1

Row{

anchors.centerIn: parent

spacing:40

Button{

label: "File"

id: fileButton

...

onButtonClick: menuListView.currentIndex = 0

}

Button{

id: editButton

label: "Edit"

...

onButtonClick: menuListView.currentIndex = 1

}

}

}

菜单栏可以通过flick操作或点击上面的菜单名称按钮.切换菜单效果很直观生动.



构造文本编辑区

声明TextArea

如果不包含文本编辑区域应用程序是不完整的.QML的TextEdit元素是多行可编辑的文本区域.TextEdit与Text元素不同,Text不允许用户直接编辑文字.

TextEdit{

id: textEditor

anchors.fill:parent

width:parent.width; height:parent.height

color:"midnightblue"

focus: true

wrapMode: TextEdit.Wrap

onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle)

}

编辑器有一系列字体颜色属性集和文字范围集.TextEdit区域放在一个flick区域上,如果文字光标在可视范围之外则可滚动文字.ensureVisible()函数用来检查光标是否在可视区域之外,并适当的移动文本区域.QML使用JavaScript作为其脚本语言,如前所述,JavaScript文件可以导入到QML文件中.

function ensureVisible(r){

if (contentX >= r.x)

contentX = r.x;

else if (contentX+width <= r.x+r.width)

contentX = r.x+r.width-width;

if (contentY >= r.y)

contentY = r.y;

else if (contentY+height <= r.y+r.height)

contentY = r.y+r.height-height;

}

为文本编辑器组合组件

现在已经使用QML创建了文本编辑器布局.文本编辑器有两个组件,已经创建的菜单栏和文本区域.QML中可以重用组件,因此导入组件并在必要时自定义组件,可使我们的代码更加简洁.文本编辑器将窗口分为两个部分:屏幕的三分之一用于菜单栏,三分之二显示文本区域.菜单栏显示在其他元素之前.

Rectangle{

id: screen

width: 1000; height: 1000

//the screen is partitioned into the MenuBar and TextArea. 1/3 of the screen is assigned to the MenuBar

property int partition: height/3

MenuBar{

id:menuBar

height: partition

width:parent.width

z: 1

}

TextArea{

id:textArea

anchors.bottom:parent.bottom

y: partition

color: "white"

height: partition*2

width:parent.width

}

}

通过导入可重用组件,我们的文本编辑器代码看起来很简单.可以自定义主应用程序,不必再过多考虑已经定义行为的属性.使用这种方式,很容易创建应用程序布局和UI组件.

美化文本编辑器Decorating the Text Editor

实现弹性界面(Drawer Interface)

现在文本编辑器看起来很简单,我们需要美化一下.使用QML,可为文本编辑器声明转换(transition)和动画.菜单栏占用了三分之一的屏幕区域,最好能在我们需要的时候才显示.

我们可以添加一个弹性界面(drawer interface),当点击的时候可弹出或收缩菜单栏.在我们的实现中,只有一个小矩形响应鼠标点击.应用程序具有两个状态:菜单栏打开和菜单栏关闭状态.弹出项是一个具有很小高度的矩形.其中嵌入了一个Image元素,具中显示一个箭头图标.弹出信息保存在应用程序中标识屏幕状态,当鼠标区域点击时对其赋值.

Rectangle{

id:drawer

height:15

Image{

id: arrowIcon

source: "images/arrow.png"

anchors.horizontalCenter: parent.horizontalCenter

}

MouseArea{

id: drawerMouseArea

anchors.fill:parent

onClicked:{

if (screen.state == "DRAWER_CLOSED"){

screen.state = "DRAWER_OPEN"

}

else if (screen.state == "DRAWER_OPEN"){

screen.state = "DRAWER_CLOSED"

}

}

...

}

}

状态用于收集配置信息,使用State元素声明.多个状态可组织在states属性中.本程序中,定义了两个状态,为DRAWER_CLOSED 和DRAWER_OPEN. 项目配置声明在PropertyChanges元素中.在DRAWER_OPEN状态中,有四个项目的属性被改变.第一个是菜单栏,修改其y值为0.同样,textArea的位置变低.textArea,drawer和drawer中的图标,属性都会变化.

states:[

State {

name: "DRAWER_OPEN"

PropertyChanges { target: menuBar; y: 0}

PropertyChanges { target: textArea; y: partition + drawer.height}

PropertyChanges { target: drawer; y: partition}

PropertyChanges { target: arrowIcon; rotation: 180}

},

State {

name: "DRAWER_CLOSED"

PropertyChanges { target: menuBar; y:-height; }

PropertyChanges { target: textArea; y: drawer.height; height: screen.height - drawer.height }

PropertyChanges { target: drawer; y: 0 }

PropertyChanges { target: arrowIcon; rotation: 0 }

}

]

状态改变有些唐突需要更加平滑的转换(transition).状态间的转换使用Transition元素定义,可绑定项目的转换属性.文本编辑器的状态可能转换到 DRAWER_OPEN 或DRAWER_CLOSED.注意,转换需要一个from和一个to状态,但本例中使用*号表示转换应用到所有状态变化.

转换中,可以设计属性变化的动画.菜单栏切换位置从y:0到y:-pertition,我们可以使用NumberAnimation元素驱动变换.我们声明了动画的目标属性并设置动画延续时间及趋势曲线.趋势曲线控制动画速度和状态转换的差值行为.这里使用 Easing.OutQuint作为趋势曲线,在动画结束时移动速度变慢.请见QML's Animation文档.

transitions: [

Transition {

to: "*"

NumberAnimation { target: textArea; properties: "y, height"; duration: 100; easing.type:Easing.OutExpo }

NumberAnimation { target: menuBar; properties: "y"; duration: 100; easing.type: Easing.OutExpo }

NumberAnimation { target: drawer; properties: "y"; duration: 100; easing.type: Easing.OutExpo }

}

]

让属性变化具有动画效果的另外一种方式是使用Behavior元素.转换(transition)只能用于状态(state)见得变化,Behavior可以设置一个一般的属性变化实现动画.在文本编辑器中,箭头使用NumberAnimating动画来设置器旋转属性.

In TextEditor.qml:

Behavior{

NumberAnimation{property: "rotation";easing.type: Easing.OutExpo }

}

回顾组件的states和动画知识,有助于改善组件的外观.在Button.qml中,可在按钮点击时增加颜色和缩放属性变化.Color类型的动画效果使用ColorAnimation,数值类型动画效果使用NumberAnimation.下面的范例中,属性名称用于设置唯一的属性.

In Button.qml:

...

color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor

Behavior on color { ColorAnimation{ duration: 55} }

scale: buttonMouseArea.pressed ? 1.1 : 1.00

Behavior on scale { NumberAnimation{ duration: 55} }

另外,给QML组件添加颜色效果,如渐变和透明效果可以增强外观效果.定义Gradient元素会覆盖元素的颜色属性.可使用GradientStop元素在渐变中声明一个颜色.渐变使用比例进行定位,值在0.0到1.0之间.

In MenuBar.qml

gradient: Gradient {

GradientStop { position: 0.0; color: "#8C8F8C" }

GradientStop { position: 0.17; color: "#6A6D6A" }

GradientStop { position: 0.98;color: "#3F3F3F" }

GradientStop { position: 1.0; color: "#0e1B20" }

}

这个渐变用于菜单栏显示一个模拟的渐变深度.第一个颜色值从0.0开始,最后的颜色值在1.0结束.

继续另外的内容

最后我们定义了一个简单的文本编辑器用户界面.接着我们可以使用Qt和C++来实现应用程序的逻辑了.QML是一个很棒的原型工具,将UI设计与应用程序逻辑分离.

使用C++扩展QML

现在完成了文本编辑器的布局,需要在C++中实现文本编辑器的功能了.将QML与C++结合我们可在Qt中创建应用程序的逻辑.在C++应用程序中可使用 Qt's Declarative类创建QML上下文,并在图像场景中显示QML元素.或者导出C++代码生成qmlviewer工具可读的插件.本例中,要在C++中实现导入和保存功能,并导出一个插件.这样我们只需直接加载QML文件,而不是执行可执行文件.

向QML导出C++类

我们使用Qt和C++来实现加载和保存功能.C++类和函数在注册后可用于QML中.类需要编译为Qt插件,而且QML文件需要知道插件的位置.

在我们的应用程序中,需要创建如下项目:

1. 处理目录相关操作的Directory类

2. 模拟目录中文件列表的文件类,继承于QObject

3. 要注册到QML上下文中的插件类

4. 编译插件的Qt项目文件

5. 告诉qmlviewer工具从哪里可发现插件的qmldir文件

生成Qt插件

要生成插件,需要在Qt项目文件中做如下设置.首先,将必要的源文件,头文件,和Qt模块添加到项目文件中.所有的C++代码和项目文件都在filedialog目录中.

在filedialog.pro中:

TEMPLATE = lib

CONFIG += qt plugin

QT += declarative

DESTDIR += ../plugins

OBJECTS_DIR = tmp

MOC_DIR = tmp

TARGET = FileDialog

HEADERS += directory.h \

file.h \

dialogPlugin.h

SOURCES += directory.cpp \

file.cpp \

dialogPlugin.cpp

特别的使用declarative模块并配置为插件使用库(lib)模版来编译Qt.将编译后的插件放在上级plugins目录中.

向QML注册类

在 dialogPlugin.h中:

#include <QtDeclarative/QDeclarativeExtensionPlugin>

class DialogPlugin : public QDeclarativeExtensionPlugin

{

Q_OBJECT

public:

void registerTypes(const char *uri);

};

在我们的插件类中,DialogPlugin继承于QDeclarativeExtensionPlugin.我们需要实现继承的函数 registerTypes().dialogPlugin.cpp代码如下:

DialogPlugin.cpp:

#include "dialogPlugin.h"

#include "directory.h"

#include "file.h"

#include <QtDeclarative/qdeclarative.h>

void DialogPlugin::registerTypes(const char *uri){

qmlRegisterType<Directory>(uri, 1, 0, "Directory");

qmlRegisterType<File>(uri, 1, 0,"File");

}

Q_EXPORT_PLUGIN2(FileDialog, DialogPlugin);

registerTypes()函数向QML中注册我们的File和Directory类.这个函数需要一个类名称作为模版参数的类别,一个主版本号,一个次版本号,和我们的类的名称.

我们需要使用Q_EXPORT_PLUGIN2宏导出插件.注意dialogPlugin.h文件中,在类声明的最前面加入了Q_OBJECT宏.同时需要在项目文件上使用qmake生成必要的元对象代码.

在C++类中创建QML属性

我们可以使用C++和Qt的元对象系统(Qt's Meta-Object System)创建QML元素和属性.可使用信号和槽实现属性,使这些属性在Qt中有效.这些属性可用于QML.

在文本编辑器中,我们需要加载和保存文件.通常,这个功能需要一个文件对话框.幸运的是我们可以使用QDir,QFile和QTextStream来实现读取目录和输入输出流.

class Directory : public QObject{

Q_OBJECT

Q_PROPERTY(int filesCount READ filesCount CONSTANT)

Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged)

Q_PROPERTY(QString fileContent READ fileContent WRITE setFileContent NOTIFY fileContentChanged)

Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )

...

Directory类使用Qt的元对象系统注册完成文件操作的属性.Directory类作为插件,在QML中为Directory元素.属性列表中使用Q_PROPERTY宏声明的都是QML属性.

Q_PROPERTY连同读写函数将属性声明到Qt的元对象系统.例如,QString类型的filename属性,可使用filename()函数读取,使用setFilename()函数设置.另外还有个与filename属性有关的信号叫做filenameChanged(),属性修改时被触发.读写函数在头文件中的public段中声明.

同样,我们还声明了其他属性.filesCount属性指示目录中的文件数量.filename属性设置当前选中的文件名称,加载或保存的文件内容存储在fileContent属性中.

Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )

files属性是目录中被过滤的文件列表.Directory类中过滤掉非文本文件.只有后缀为.txt的文件有效.而且,在C++中声明的QDeclarativeListProperty属性在QML中可作为QList使用.这个模版对象需要从QObject继承,因此,File类必须从QObject继承.在Directory类中,文件对象列表存储在叫做m_fileList的QList中.

class File : public QObject{

Q_OBJECT

Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)

...

};

这些属性可在QML中作为Directory元素的属性.注意我们没有在C++中创建唯一标识id属性.

Directory{

id: directory

filesCount

filename

fileContent

files

files[0].name

}

因为QML使用Javascript语法和结构体,我们可以遍历文件列表并获取其属性.如获取第一个文件属性,可以调用files[0].name.

通常C++函数也可在QML中访问.文件加载和保存的函数在C++中使用Q_INVOKABLE 宏定义.而且,我们可以讲函数声明为槽,函数可在QML中直接访问.

In Directory.h:

Q_INVOKABLE void saveFile();

Q_INVOKABLE void loadFile();

Directory类也会在文件内容发生变化的时候通知其他类.这个特性是使用信号实现的.如前面提到的,QML信号有一个相应的处理器,名称以on为前缀.信号叫做directoryChanged,在目录更新时触发.更新操作简单的从新加载目录内容并更新目录中有效的文件列表.QML项目设置onDirectoryChanged信号的处理槽函数就可得到通知.

列表属性需要特殊对待.因为列表属性使用回调来方位和修改列表内容.列表属性是QDeclarativeListProperty<File>类型.当访问列表时,访问器函数返回一个QDeclarativeListProperty<File>对象.模版类型File必须从QObject继承.而且,要创建QDeclarativeListProperty,列表的访问器和修改器需要传递构造函数作为函数指针.本例中列表为QList,需要存放File指针.

QDeclarativeListProperty的构造函数和Directory实现:

QDeclarativeListProperty ( QObject * object, void * data, AppendFunction append, CountFunction count = 0, AtFunction at = 0, ClearFunction clear = 0 )

QDeclarativeListProperty<File>( this, &m_fileList, &appendFiles, &filesSize, &fileAt, &clearFilesPtr );

构造函数传递一个可添加列表,返回列表数量,使用索引获取项,情况列表的函数指针.只有添加函数式必须的.注意函数指针必须与 AppendFunction, CountFunction, AtFunction, or ClearFunction匹配.

void appendFiles(QDeclarativeListProperty<File> * property, File * file)

File* fileAt(QDeclarativeListProperty<File> * property, int index)

int filesSize(QDeclarativeListProperty<File> * property)

void clearFilesPtr(QDeclarativeListProperty<File> *property)

为简化文件对话框,Directory类将不以.txt为后缀的文件过滤掉.如果文件名称不以.txt为后缀,就不能在file对话框中可见.同样保存文件的实现确保在文件名后加上.txt后缀.Directory使用QTextStream读取文件并向文件中输出内容.

使用Directory元素,可以获取文件列表,知道应用程序目录中的文本文件数量,获取文件名称和文件内容字符串,当目录内容发生变化时得到通知.

生成插件,在filedialog.pro上运行qmake,然后运行make生成插件并拷贝到plugins目录.

在QML中导入插件

qmlviewer工具导入与应用程序同目录的文件.也可创建qmldir文件来指定希望导入的QML文件位置.qmldir文件可也指定插件位置和其他资源.

qmldir文件内容:

Button ./Button.qml

FileDialog ./FileDialog.qml

TextArea ./TextArea.qml

TextEditor ./TextEditor.qml

EditMenu ./EditMenu.qml

plugin FileDialog plugins

插件正是我们上面创建的FileDialog,在项目文件中指定了TARGET域.插件编译后的文件在plugins目录中.

向文件菜单整合文件对话框

文件菜单FileMenu需要显示一个FileDialog元素,其中显示了目录中含有的文件列表,用户可在列表中点击选择文件.同时也要设置保存,加载和新建按钮的响应事件.FileMenu中包含一个可编辑的文本输入框,让用户使用键盘输入文件名称.

Directory元素在FileMenu.qml文件中使用,并在目录中内容刷新时通知FileDialog元素.通知是使用onDirectoryChanged信号实现的.

In FileMenu.qml:

Directory{

id:directory

filename: textInput.text

onDirectoryChanged: fileDialog.notifyRefresh()

}

为了简化应用程序,文件对话框总是显示,并过滤掉不已.txt为后缀的文件.

In FileDialog.qml:

signal notifyRefresh()

onNotifyRefresh: dirView.model = directory.files

FileDialog元素显示从files属性中读取的目录内容.files作为GridView(可将代理内容显示在网格中)元素的模型.由代理处理模型的外观,而文件对话框只简单的在中间生成一个带有文字的网格.点击文件名称使文件名称矩形高亮.FileDialog在notifyRefresh信号触发时得到通知,重新加载目录中的文件.

FileMenu.qml:

Button{

id: newButton

label: "New"

onButtonClick:{

textArea.textContent = ""

}

}

Button{

id: loadButton

label: "Load"

onButtonClick:{

directory.filename = textInput.text

directory.loadFile()

textArea.textContent = directory.fileContent

}

}

Button{

id: saveButton

label: "Save"

onButtonClick:{

directory.fileContent = textArea.textContent

directory.filename = textInput.text

directory.saveFile()

}

}

Button{

id: exitButton

label: "Exit"

onButtonClick:{

Qt.quit()

}

}

FileMenu现在具备了各种功能.saveButton将TextEdit中的文本传递给目录文件的Content属性,并从可编辑的文本输入元素中复制文件名称.最后,按钮调用saveFile()函数,保存文件.loadButton执行方式同理.新建动作清空TextEdit的内容.

EditMenu中的按钮调用TextEdit的函数来拷贝,粘贴,全选文本编辑器中的内容.

完成文本编辑器

应用程序已经可以作为简单的文本编辑器了,可输入文本并保存到文件中.也可从文件中加载并手动修改文本.

运行文本编辑器

在运行文本编辑器前需要编译C++插件(文件对话框).要编译,进入gsQml目录,运行qmake,依据你使用的平台在使用make或nmake进行编译.启动qmlviewer,打开texteditor.qml文件.

源码在examples/tutorials/gettingStarted/gsQml目录中.

在QML中显示QGraphicsWidget 类,需要使用qmlRegisterType().范例Extending QML Functionalities using C++展示更多在QML中使用C++的信息.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: