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---