Release1.0 http://qt-project.org/wiki/developer-guides
Qt Quick Application Developer Guide for Desktop
这个教程的目的是让你熟悉使用QtQuick构建QML程序的最佳编程实践方法; 先决条件: 对QML有相当的理解, 相关阅读: <qtquick/qtquick-applicationdevelopers.html>; 本教程会涉及QML开发最佳实践的各个方面, 以及在典型的桌面环境下部署应用的情况; 參阅很多其它相关的信息源能够让你对QML编程的理解更深刻;
CHAPTER1 关于教程
1.1 为何阅读
这份教程对于开发丰富特性的应用以及在多种桌面平台部署应用的情况, 提供了一个QML和QtQuick技术总览;
重点在于QtQuick和怎样有效地编写整个应用而不使用C++; 教程一步步指导你怎样初始化开发环境, 配置一个能够部署的新项目; 这里有一个简单应用(NoteApp);
这里有很多章节, 每一步都会特定描写叙述应用的特性, 开发方式和QML代码细节; 应用覆盖了各个方面, 比如高级UI概念, 包含animation, database storage和Javascript应用程序逻辑;
这个应用看起来不会像典型或经典的桌面程序, 普通的UI元素比方 toolbar, menu, dialog之类没有被使用; 这个应用是受到现代的流UI(fluid UIs)启示, 目标是桌面环境;
为了方便描写叙述, NoteApp* 相应每一个章节有自己的版本号; 建议阅读教程的时候參考代码;
教程结束时, 你应该有对使用QML语言, 利用QML/QtQuick技术开发程序有一个深入理解;
NoteApp*程序的截图;
1.2 源码
略
1.3 License
略
---1End---
CHAPTER2 设计原型和起始设计
QtQuick和QML的一大长处是, 它使得你能够高速开发原型; 考虑Prototyping阶段作为开发NoteApp程序的第一步有两个原因: 1) 前面提到过, QML让我们能够高速开发原型, UI设计师能够不费劲地草绘出一些初始化一些UI屏幕; 2) 原型能够让你和设计者紧密合作, UI概念的应用将在几个非常短的迭代过程中完毕;
接下去, 这个原型将作为继续开发的基础; 这章中, 我们将引导你实施开发阶段, 包含UI概念, 特性集合, UI交互流程, 一些初始化的QML屏幕作为原型的一部分; 当中会有一些主要QML概念的介绍, 比方创建QML组件以及QML item布局;
这章主要讨论点的简要列表:
- UI概念和特性集合
- 使用QtCreator创建QML组件
- 使用Anchor和Repeater类型来给UI元素布局
2.1 NoteApp程序概览
NoteApp程序是一个便利贴(Post-it note) [http://en.wikipedia.org/wiki/Post-it_note]程序, 帮助用户创建note而且在本地存储; 假设note有一个类别(category)的话, 就更易管理, 因此考虑有三种不同的类别; 依据视觉的角度来看, 一个类别能够被一个区域表示; 我们来介绍一下Page的概念; 一个Page是一个区域, 在这个区域中能够创建note而且放置进去;
用户应该能够一个一个地删除note, 也能够一次性所有删除; note能够自由地在Page区域中移动; 为了简化, 我们定义三个Page然后使用 Marker来识别每张Page; 另外, 每一个marker能够有不同的颜色;
一个有趣的特性是本地存储note, 并且可能是自己主动完毕, 不用询问用户是否存储;
总结下特性:
- 创建/删除 Note item;
- 编辑Note和在page中任何位置放置note
- 本地存储note
- 三个不同page由一个page marker表示
2.1.1 UI元素
基于前面讨论的需求, 我们从一个线框(wire-frame)设计開始; 因为NoteApp可能有非常多种的设计, 让我们考虑採用当中一种;
从UI的角度看, 上面的图片给出了用户想要的样子, 并且它也能帮助你找到可能的UI元素, 以及能够应用的交互;
2.1.2 UI流程(Flows)
如前面所提到的, 有三个Page能够包括Note item; 我们也能够在右边看到Marker, 左边看到toolbar; toolbar包括: New Note工具--创建新的note; Clear All工具--清除整个page; Note item有一个toolbar能够用来拖拽(drag)note, 按下鼠标左键, 移动鼠标能够在page中拖动note; 另外, note toolbar上另一个 delete工具能够删除note;
下一步
?要确认特性中须要实现的QML组件, 以及怎样创建它们;
2.2 为UI元素创建一个QML组件
一旦我们恰当地定义了特性集合和UI概念, 确认了主要的UI元素, 就能够安全地開始实现NoteApp的原型;
原型能够是很基础的UI, 没有不论什么功能, 可是它提供了这个程序在经过迭代实现后, 在完毕时看上去的大概样子;
这一步中, 你会找到使用QtCreator创建QtQuick UI的细节, 但最重要的是, 怎样确定和创建一个QML组件;
2.2.1 QtCreator中创建一个QtQuick UI项目
在原型阶段, 非常重要的一点是, 创建一个Qt Quick UI项目是推荐的方式, 是一个有效的方法; 这样的方式下, prototyping, 特别是开发和測试每一个独立的QML组件更简单; 独立地測试每一个新建的组件是非常重要的, 这样你才干立马定位错误, 使用QtQuick UI项目让这一切更简单;
很多其它细节參考 Creating a Qt Quick UI http://qt-project.org/doc/qtcreator-2.6/quick-projects.html#creating-qt-quick-ui-projects;
Note: 总是要有一个QML文件, 定义为主文件来载入和执行程序; 对于NoteApp, 我们有main.qml, 它是由QtCreator产生的文件;
2.2.2 识别作为UI元素的QML组件
假设想要和面向对象编程做一个类比, QML组件能够看作类, 用来定义和实例化对象; 你可能会用一个大的QML文件来写一整个简单程序, 可是那样可能会添加复杂性, 使得代码重用性和维护十分困难--有些甚至是不可能;
QML组件能够被看成是一个普通UI元素的小组; 很多其它情况下, 它代表一个UI元素以及提前定义的action和property;
基于我们的UI概念和设计, 这里有一个, 这里有一个自己定义QML组件列表, 在接下去的迭代中会用到;
Note: 每一个QML组件使用自己的QML文件(.qml), qml文件和组件的名字一样; 比如, Note组件会命名为 Note.qml;
- Note 代表note item;
- Page 这个组件包括item;
- Marker 代表一个page marker, 让户能够使用marker在page之间切换;
- NoteToolbar 在note item上的toolbar用来拖拽和布局
很多其它使用QtCreator创建组件的细节參考: Creating QML Components with QtCreator3 http://qt-project.org/doc/qtcreator-2.6/quick-components.html
下一步
接下去看怎样进一步强化定义的组件以及開始实现原型UI;
2.3 Anchoring QML Item和实现QML组件
Rectangle QML类型是构建UI块的一个自然的选择, 也可作为在prototype阶段初始化QML的组件; 它是一个拥有属性的visual类型, 你能够任意调整它, 使得prototype和test更为简单;
Note: 总是使用一样的默认几何数据来定义组件是一个好习惯, 有助于測试;
让我们来看看QML组件的代码; 首先, 我们開始实现Note组件;
2.3.1 Note和 NoteToolbar组件
首先, 就像前面步骤中所见, 我们已经创建了一个新的QML文件用来实现所需的组件;
要符合线框设计, 代码看起来会是以下这样:
// NoteToolbar.qml
1
2
3
4
5
6
7
8
|
import
QtQuick 2.0 //
A Rectangle element with defined geometries and color. Rectangle
{ id:
root width:
100 height:
62 color: "#9e964a" } |
Note组件有一个toolbar UI元素 -- NoteToolBar组件; 另外, 有一个 text input元素来获取用户输入文字; 我们使用 TextEdit QML类型; 为了把这些UI元素放入Note组件, 要使用anchor属性; 这个属性继承自 Item类型--是最主要的类, 每一个QML组件默认继承自它;
很多其它布局细节參考 Anchor-based Layout in QML http://qt-project.org/doc/qt-5/qtquick-positioning-anchors.html ;
Note Anchor-based Layout不能和绝对位置混用;
// Note.qml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
import
QtQuick 2.0 Rectangle
{ id:
root width:
200 height:
200 color: "#cabf1b" //
creating a NoteToolbar that will //
be anchored to its parent on the top, left and right NoteToolbar
{ id:
toolbar //
height is required to be specified //
since there is no bottom anchoring. height:
40 //
anchoring it to the parent //
using just three anchors anchors
{ top:
root.top left:
root.left right:
root.right } } //
creating a TextEdit used for the text input from user. TextEdit
{ anchors
{ top:
toolbar.bottom bottom:
root.bottom right:
root.right left:
root.left } wrapMode:
TextEdit.WrapAnywhere } } |
Warning 因为性能原因, anchor应该仅仅用在兄弟(sibling)或直接的父类(parent)上;
2.3.2 Page
一旦Note组件好了, 就能够開始Page组件的工作, 放两个Note在里面;
// Page.qml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
Rectangle
{ id:
root width:
600 height:
400 color: "#222525" //
creating a Note item Note
{ id:
note1 //
use x and y properties to specify //
the absolute position relative to the parent x:
105; y: 144 } Note
{ id:
note2 x:
344 y:
83 } } |
在QtCreator中, 你能够简单地执行上面的文件, 实际上使用 qmlscense载入Page.qml更简单;
2.3.3 Marker
和其余的组件一样, Marker组件也使用Rectange类型和提前定义的几何形状; 后面会看到如何使用Marker组件;
// Marker.qml
1
2
3
4
5
6
|
Rectangle
{ id:
root width:
50 height:
90 color: "#0a7bfb" } |
下一步
下一章我们会看到怎样使用 Repeater QML类型和 使用Column来管理一个静态的marker列表;
2.4 使用Repeater和Delegate来创建Marker的List
在前面, 我们看到了怎样创建QML组件: Note, NoteToolbar, Page, Marker, 以及怎样使用 anchors放置QML组件;
回想之前的设计概念, 我们注意到三个 Marker元素是垂直并排的; 使用 anchors能够固定UI元素做到这个要求, 可是那样会添加代码复杂度; QML有方便的方式--layout和 positioning类型; Column类型是当中一个, 使得UI元素能够一个接一个排成一列;
由于我们想要放三个Marker组件在 Column中, 能够使用方便的QML类型--Repeater;
如今看以下的代码:
1
2
3
4
5
6
7
8
9
10
11
12
|
Column
{ id:
layout //
spacing property can set to let the item have space between them spacing:
10 //
a Repeater item that uses a simple model with 3 items Repeater
{ model:
3 delegate: //
using the Marker component as our delegate Marker
{ id: marker } } } |
上面代码中, Repeater产生三个QML组件, 基于model和delegate; 因为我们想要三个Marker item, 就简单地使用Marker组件作为 delegate;
很多其它关于 positioning的信息參阅 Important Concepts In Qt Quick - Positioning http://qt-project.org/doc/qt-5.0/qtquick/qtquick-positioning-topic.html
一个问题非常自然地出现:"上面代码要放在哪个qml文件的哪个位置?
"; 我们须要一个独立的QML组件--MarkerPanel; MarkerPanel是一个简单列表, 包括三个Marker item能够用作UI元素;
// MarkerPanel.qml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
Rectangle
{ id:
root width:
50 height:
300 //
column type that anchors to the entire parent Column
{ id:
layout anchors.fill:
parent spacing:
10 Repeater
{ //
just three Marker items model:
3 delegate: Marker
{ id: marker } } } } |
Note: 建议总是在prototype阶段对组件进行独立地执行和測试; 这样你能够立马定位错误;
执行 MarkerPanel组件:
下一步
我们将会看到怎样使用创建出来的组件来完毕原型;
2.5 完毕原型
如今QML组件都就绪了, 能够用来构建我们的原型;
- Note - NoteToolbar -Marker -MarkerPanel -Page
在接下去的阶段中可能会有很多其它QML组件出现;
前面提到过, QtCreator产生一个 main.qml能够当作main文件来载入和执行NoteApp; 因此, 我们開始在 main.qml中安排组件, 组合出一个原型;
2.5.1 组合出原型
回到UI概念, 再看一下设计, 然后開始布局; 我们有Marker的面板(panel)--MarkerPanel组件放在右边, Page组件在中间; 眼下为止还没有提到 toolbar;
toolbar包括两个工具, 一个用来创建新的note, 一个是清除Page; 为了简化, 我们不为它创建一个组件了, 直接在main.qml中定义它;
代码大致这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
//
using a Rectangle element to represent our toolbar //
it helps to align the column better with the rest of the components Rectangle
{ id:
toolbar //
setting a width because there is no right anchoring width:
50 color: "#444a4b" anchors
{ left:
window.left top:
window.top; bottom: window.bottom topMargin:
100; bottomMargin: 100 } //
using a Column type to place the tools Column
{ anchors
{ fill: parent; topMargin: 30 } spacing:
20 //
For the purpose of this prototype we simply use //a
Repeater to generate two Rectangle items. Repeater
{ model:
2 //
using a Rectangle item to represent //
our tools, just for prototype only. Rectangle
{ width: 50; height: 50; color: "red" } } } } |
如今, 能够实现我们的原型了;
// main.qml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
Rectangle
{ //
using window as the identifier for this item as //
it will the only window of the NoteApp id:
window width:
800 height:
600 //
creating a MarkerPanel item MarkerPanel
{ id:
markerPanel width:
50 anchors.topMargin:
20 anchors
{ right:
window.right top:
window.top bottom:
window.bottom } } //
the toolbar Rectangle
{ id:
toolbar width:
50 color: "#444a4b" anchors
{ left:
window.left top:
window.top bottom:
window.bottom topMargin:
100 bottomMargin:
100 } Column
{ anchors
{ fill: parent; topMargin: 30 } spacing:
20 Repeater
{ model:
2 Rectangle
{ width:
50; height:
50; color: "red" } } } } //
creating a Page item Page
{ id:
page1 anchors
{ top:
window.top bottom:
window.bottom right:
markerPanel.left left:
toolbar.right } } } |
执行起来是这种:
2.5.2 让Note组件能够拖拽
眼下我们有了一个基础的原型, 能够作为NoteApp UI的架子; 有一个酷炫的UI功能在原型阶段就能够完毕--让用户在page里拖拽note item; 要做到这点, MouseArea QML Type有个属性群叫做drag; 我们将使用 drag.target 属性, 将note组件的 id设置给它;
考虑到用户会使用 NoteToolbar来拖拽一个note, MouseArea类型应该放在 NoteToolbar组件里面; NoteToolbar会处理用户的拖拽操作, 我们应该把 drag.target设置成Note组件;
想做到这点, 我们要同意NoteToolbar在Note之中将 MouseArea的drag.target属性绑定到Note的id; QML提供了 Property Aliases http://qt-project.org/doc/qt-5/qtqml-syntax-objectattributes.html#property-aliases
来实现这一点;
我们来为NoteToolbar中MouseArea的drap属性组创建一个property alias(属性别名);
// NoteToolbar.qml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Rectangle
{ id:
root width:
100 height:
62 color: "#9e964a" //
declaring a property alias to the drag //
property of MouseArea type property
alias drag: mousearea.drag //
creating a MouseArea item MouseArea
{ id:
mousearea anchors.fill:
parent } } |
从上面的code可见, NoteToolbar的drag属性别名绑定到了MouseArea的drag属性, 如今就能够在Note组件中使用它了;
// Note.qml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
Rectangle
{ id:
root width:
200 height:
200 color: "#cabf1b" //
creating a NoteToolbar that will be anchored to its parent NoteToolbar
{ id:
toolbar height:
40 anchors
{ top:
root.top left:
root.left right:
root.right } //
using the drag property alias to //
set the drag.target to our Note item. drag.target:
root } //
creating a TextEdit TextEdit
{ anchors
{ top:
toolbar.bottom bottom:
root.bottom right:
root.right left:
root.left } wrapMode:
TextEdit.WrapAnywhere } } |
很多其它细节參考 Property Binding http://qt-project.org/doc/qt-5/qtqml-syntax-propertybinding.html
下一步
基于原型開始实现UI和基本功能;
---2End---
CHAPTER3 实现UI和加入功能
初始的原型引入的是基础的UI和NoteApp功能概念, 能够帮助我们找出所需的QML组件;
基于原型, 我们朝着更完整的UI和功能, 试着构建一个能够工作的程序; 如今, 使用眼下实现的组件開始组合出这个程序的UI;
这一章很多其它关于QML图形, 图像, 背景, 强化UI, 同一时候也使用Javascript加入方法; 你能够看到一些QML类型更深层的内容, 通过一点点添加代码复杂度, 加入功能来了解很多其它;
要点:
- 引入PagePanel组件, 使用Repeater元素管理Page元素;
- 使用QML图形;
- 更深入使用QML类型;
3.1 创建PagePanel组件
方便起见, 我们用Page组件仅仅创建一个page item; 我们已经把它放置到其它item一起, anchor到它的parent; 不管怎样, NoteApp概念和需求是要求三个不同page, 然后用户能够使用marker导航(navigate)到不论什么一个; 我们能够看到MarkerPanel组件怎样帮助我们创建和布局三个Marker item, 接下去能够使用同样方法处理Page item, 这样实现PagePanel组件;
3.1.1 使用一个Item类型
在我们更进一步之前, 理解为什么Rectangle类型在组件中作为顶层这种设计, 如今開始要防止出现; 原因是我们使用Rectangle元素是由于它能够帮助我们迅速得到可视化的结果, 这也是原型阶段的需求;
但一旦原型完毕, 用 Item类型替换 Rectangle类型会更合理, 特别当考虑到拿图形作为背景的UI元素;
// Page.qml
1
2
|
Item
{ id:
root |
Warning: 从如今開始, 考虑用 Item QML类型来作为组件的顶层元素; 參考每一章的源码;
3.1.2 使用States
继续PagePanel组件的开发, 可见PagePanel和MarkerPanel之间基本的差别是三个Marker item应该总是可见的, 可是Page item同一时候仅仅能有一个是可见的; 这依赖于用户点选了哪个Marker item;
实现这个行为能够有多种方式; 当中一个是inline(内联) Javascript方法, 依据当前用户点选的Marker切换(toggle) Page item的可见性;
在NoteApp里面, 我们使用了 State类型实现所须要的行为; PagePanel组件有三个state, 每一个都绑定到一张Page的 visible属性; 因此在page之间切换的事件时候就转换成了在PagePanel里面设置独立的状态;
首先, 在Page组件里, 我们要设置 opacity(透明度)属性为0.0作为默认值; 这是为了让page在初始的时候不可见, 然后基于各自的state改变让它们变得可见;
// Page.qml
1
2
3
4
5
|
Item
{ id:
root opacity:
0.0 ... } |
一旦创建了PagePanel.qml文件, 就能够開始创建state和Page item; 我们须要三个state:
- personal fun work
这会改变以下每一个Page相应各自的opacity属性: personalpage funpage workpage
以下的图示展示了state关联到各自的page;
// PagePanel.qml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
Item
{ id:
root //
creating the list of states states:
[ //
creating a state item with its corresponding name State
{ name: "personal" //the
properties that are about to change for a defined target PropertyChanges
{ target:
personalpage opacity:1.0 restoreEntryValues: true } }, State
{ name: "fun" PropertyChanges
{ target:
funpage opacity:1.0 restoreEntryValues: true } }, State
{ name: "work" PropertyChanges
{ target:
workpage opacity:1.0 restoreEntryValues: true } } ] //
creating three page items that are anchored to fill the parent Page
{ id: personalpage; anchors.fill: parent } Page
{ id: funpage; anchors.fill: parent } Page
{ id: workpage; anchors.fill: parent } } |
Note: 设置 restoreEntryValues 属性为true能够让target变化后的属性重设成默认值, 这样当state变化的时候, page的opacity属性能够被重设为false;
观察上面的代码, 能够看到三个Page item创建出来了, 不同的state会改变这些item的opacity属性; 这一步, 我们设法创建了一个新的组件--PagePanel, 能够帮助我们使用三个state切换页面;
下一步
使用Marker item变换PagePanel item的state;
---TBC---