QtQuick桌面應用程序開發指南 4)動態管理Note對象_B 5)增強外觀 6)許多其餘的改進

4.2.2 Stateless(無論狀態)JavaScript庫
web

爲了讓開發輕鬆點, 使用一個JavaScript接口來和數據庫交互是個好主意, 它在QML中提供了方便的方法;
數據庫

在QtCreator中建立一個新的JavaScript文件 noteDB.js, 保證選擇了 State Library選項; 這樣使得noteDB.js用起來像一個庫, 提供了stateless的helper方法; 這樣,在每個QML組件 import noteDB.js以及使用它的時候, 就僅僅有一份實例被載入和使用; 這也保證了僅僅有一個全局變量用來存儲數據庫實例: _db;數組

Note Non-stateless(非狀態無關的)JavaScript文件在導入一個QML組件的時候很是實用, 可以對那個組件運行操做, 所有的變量僅僅有在那份context(上下文)中有效; 每次import都會建立一個獨立的JavaScript文件的實例;
安全

noteDB.js應該提供如下的功能:
app

- 打開/建立一個本地數據庫實例less

- 建立必須的數據庫table(表格)工具

- 從數據庫讀取notes性能

- 刪除所有的notes字體

接下來會看到 關於noteDB.js中的方法怎樣被實現的不少的細節--讀取, 存儲note item到數據庫的實現; 先考慮如下的方法實現:
動畫

- function openDB() - 假設數據庫不存在, 就建立一個, 存在就打開它;

- function createNoteTable() - 假設note table不存在就建立note table; 這種方法僅僅會在 openDB()中調用;

- function clearNoteTable() - 從note table中移除所有的行(row);

- readNotesFromPage(markerId) - 這個helper方法讀取所有和特定markerId相關聯的note, 返回一個dictionary(字典)類型的數據;

- function saveNotes(noteItems, markerId) - 用來在數據庫中保存note item; noteItem參數表明一個note item的list(列表), markerId表明note item隸屬的那個對應的page;

Note 所有這些JavaScript方法使用Qt Quick Local Storage API來獲取數據庫, 聲明語句(statement) import QtQuick.LocalStorage 2.0 as Sql, 要寫在 noteDB.js的開始; 

4.2.3 讀取和存儲Note

實現了noteDB.js以後, 可以用法來讀取和存儲note了; 

一個好的作法是在main.qml文件裏, 在初始化的時候或打開數據庫鏈接的時候纔去調用; 這樣咱們可以使用定義在noteDB.js中的JavaScript方法而不會又一次初始化(reinitializing)數據庫;

在main.qml裏面導入noteDB.js 和 QtQuick.LocalStorage 2.0, 有個問題是何時調用 openDB()方法; QML提供了helpful attached signal: onCompleted()和 onDestruction(), 會在Component被全然載入全然銷燬的時候各自emit(發送);

// main.qml

1
2
3
4
5
6
7
8
9
import QtQuick 2.0
import  "noteDB.js"  as NoteDB
 
Item {
     // this signal is emitted upon component loading completion
     Component.onCompleted: {
         NoteDB.openDB()
     }
//...

如下是openDB方法實現, 它調用 openDatabaseSync()方法來建立數據庫, 以後調用 createNoteTable()方法來建立note表;

//noteDB.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.pragma library
.import QtQuick.LocalStorage 2.0 as Sql
// declaring a global variable for storing the database instance
var  _db
 
function  openDB() {
     print( "noteDB.createDB()" )
     _db = openDatabaseSync( "StickyNotesDB" , "1.0" ,
             "The stickynotes Database" , 1000000);
     createNoteTable();
}
 
function  createNoteTable() {
     print( "noteDB.createTable()" )
     _db.transaction(  function (tx) {
         tx.executeSql(
             "CREATE TABLE IF NOT EXISTS note
             (noteId INTEGER PRIMARY KEY AUTOINCREMENT,
             x INTEGER,
             y INTEGER,
             noteText TEXT,
             markerId TEXT)" )
     })
}

在main.qml裏面, 初始化了數據庫, 安全地開始從Page組件中載入Note item; 上面咱們提到了 readNotesFromPage(markerId)方法, 返回一個data array(數組)list (參照script world--腳本世界裏的dictionary), 每個數組表明數據庫中的一行--note的數據; 

//noteDB.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//...
function  readNotesFromPage(markerId) {
     print( "noteDB.readNotesFromPage() "  + markerId)
     var  noteItems = {}
     _db.readTransaction(  function (tx) {
         var  rs = tx.executeSql(
             "SELECT FROM note WHERE markerId=?
             ORDER BY markerid DESC" , [markerId] );
         var  item
         for  ( var  i=0; i< rs.rows.length; i++) {
             item = rs.rows.item(i)
             noteItems[item.noteId] = item;
         }
     })
     return  noteItems
}

Page組件會讀取note而後建立對應的QML note對象;

// Page.qml

1
2
3
4
5
6
7
8
9
10
11
//...
// when the component is loaded, call the loadNotes()
// function to load notes from the database
Component.onCompleted: loadNotes()
// a Javascript helper function that reads the note data from database
function  loadNotes() {
     var  noteItems = NoteDB.readNotesFromPage(markerId)
     for  ( var  in  noteItems) {
         newNoteObject(noteItems[i])
     }
}

咱們可以看到 newNoteObject()方法在以前的Page.qml定義, 它獲得一個data數組做爲參數, 實際上就是 x, y, noteText, markerId和 noteId屬性的值;

Note 注意note表的name域(field)和Note組件的屬性同樣; 這可以幫助咱們把table行的數據做爲參數傳遞, 建立note QML對象; 

現在咱們實現了從數據庫載入Note item的方法, 下一個邏輯步驟是實現一個將note存儲到DB中的方法; PagePanel組件是負責建立Page item的, 所以它應該可以從每張page上讀取note, 而後調用 saveNote() JS方法來存儲note;

//noteDB.js

1
2
3
4
5
6
7
8
9
10
11
12
//...
function  saveNotes(noteItems, markerId) {
     for  ( var  i=0; i<noteItems.length; ++i) {
         var  noteItem = noteItems[i]
         _db.transaction(  function (tx) {
             tx.executeSql(
             "INSERT INTO note (markerId, x, y, noteText)
             VALUES(?

,?

,?,?

)",

             [markerId, noteItem.x, noteItem.y, noteItem.noteText]);
         })
     }
}

首先咱們定義一個屬性alias可以將Note item暴露出來, 這些item是做爲在Page組件中container的children而建立的;

// Page.qml

1
2
3
4
5
//...
// this property is used by the PagePanel component
// for retrieving all the notes of a page and storing
// them in the Database.
property alias notes: container.children

在PagePanel中實現在DB中保存note的功能;

// PagePanel.qml

1
2
3
4
5
6
7
8
9
10
11
//...
     Component.onDestruction: saveNotesToDB()
     // a JavaScript function that saves all notes from the pages
     function  saveNotesToDB() {
         // clearing the DB table before populating with new data
         NoteDB.clearNoteTable();
         // storing notes for each individual page
         NoteDB.saveNotes(personalpage.notes, personalpage.markerId)
         NoteDB.saveNotes(funpage.notes, funpage.markerId)
         NoteDB.saveNotes(workpage.notes, workpage.markerId)
     }

爲了下降代碼複雜度, 咱們在保存note以前清楚DB中所有的數據; 這樣可以不用謝代碼來更新現存的Note item;

在結束本章前, 用戶可建立和刪除note, 程序也可以本身主動保存和載入note;

下一步

下一章介紹一些美麗的動畫, 以及實現的步驟和技巧;

---4End---


CHAPTER5 外觀增強

NoteApp的UI可以看做依照功能和用戶交互實現的; 事實上還有很是大的進步空間, 讓UI更吸引用戶; QML設計成一種聲明式語言, 記得用動畫和UI的流暢過分; 

這一章會一步步實現動畫, 讓NoteApp感受更優美(fluid); QML提供了一組QML類型以各類方式實現動畫; 這一章會介紹一些新的類型, 在QML組件中使用它們, 讓UI更加流暢;

總體上, 本章覆蓋如下幾點:

- 介紹animation(動畫)和transition(過渡)效果;

- 新的QML類型: Behavior, Transition, 各類Animation元素;

- 使用各類動畫強化NoteApp的QML組件;

5.1 NoteToolbar動畫

咱們來看看怎樣改進Note組件, 加入一些基於用戶交互的行爲; Note有一個toolbar, 可以用Delete tool刪除筆記; toolbar是用來按下鼠標四處拖拽note用的;

一個改進是可以讓Delete tool僅僅有在需要的時候可見; 好比, 在toolbar上懸停(hover)的時候讓Delete工具可見, 使用 fade-in/fade-out(漸入漸出)效果更美麗;

Note opacity屬性的值是從parent到child item傳播的(propagated); [屬性繼承]

Behavior類型贊成咱們定義一個基於item的屬性變化的行爲; 

// NoteToolbar.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
//...   
     MouseArea {
         id: mousearea
         anchors.fill: parent
         // setting hoverEnabled property to true
         // in order for the MouseArea to be able to get
         // hover events
         hoverEnabled:  true
     }
 
     // using a Row element for laying out tool
     // items to be added when using the NoteToolbar
     Row {
         id: layout
         layoutDirection: Qt.RightToLeft
         anchors {
             verticalCenter: parent.verticalCenter;
             left: parent.left;
             right: parent.right
             leftMargin: 15;
             rightMargin: 15
         }
         spacing: 20
         // the opacity depends if the mousearea
         // has the cursor of the mouse.
         opacity: mousearea.containsMouse ? 1 : 0
         // using the behavior element to specify the
         // behavior of the layout element
         // when on the opacity changes.
         Behavior on opacity {
             // using NumberAnimation to animate
             // the opacity value in a duration of 350 ms
             NumberAnimation { duration: 350 }
         }
     }
//...

上面代碼可以看到, 咱們啓用MouseArea的hoverEnabled屬性, 接收鼠標懸停事件; 以後, 咱們可以把Row類型的opacity切換到0, 假設MouseArea類型沒有懸停(hovered)就設爲1; MouseArea的containsMouse屬性用來決定opacity的值; 

這樣Behavior類型在Row中被建立出來, 基於opacity屬性而定義它的行爲; 當opacity改變時, NumberAnimation會被應用; 

NumberAnimation類型應用基於一個數字值的改變, 咱們對Row的opacity屬性, 在350毫秒時長內顯示它;

Note NumberAnimation類型是繼承自PropertyAnimation, 它有一個 Easing.Linear做爲 easing curve動畫的默認值; [動畫效果, 執行軌跡等...]

下一步

咱們可以看到怎樣使用Transition和其它QML Animation實現動畫效果;


5.2 使用State和Transition

前面咱們看到的定義簡單的話的技巧是基於屬性變化的, 使用了 Behavior和 NumberAnimation;

固然, 也有其它動畫是依賴於一組屬性變化的--可以用State實現;

現在來看看如何進一步實現NoteApp的UI;

Marker item看起來在用戶交互的時候是靜態的; 假設基於用戶交互的不一樣場景給它加上一點動畫會如何? 另外, 咱們可以可以讓當前激活的marker和當前的page對用戶來講看起來更明明白; 

5.2.1 Marker Items加上動畫

咱們差點兒相同可以總結一下在用戶交互時改進Marker item的各類可能的場景, 下面是user case(用戶用例)描寫敘述:

- 當前激活的Marker item應該更加明顯; 用戶點擊的時候, 一個marker要變成激活狀態; 激活的marker會略微變大, 可以從左邊向右邊滑動一些;(像個抽屜)

- 當用戶鼠標懸停在一個marker上面, marker從左到右滑動, 但不會像激活的marker同樣滑動;

考慮上述場景, 咱們需要在Marker和MarkerPanel組件上進行工做;

當獨當上面關於行爲需求的描寫敘述(左到右的滑動效果), 立刻出現的想法是改變 x的屬性, 因爲它表明了item在X-axis(軸)上的位置; 另外, 因爲Marker item應該要知道它本身是不是當前激活的, 所以加入一個新的屬性 active;

爲Marker組件引入兩個新的state, 表明上面描寫敘述的行爲:

- hovered: 當用戶鼠標懸停的時候會更新marker的x屬性值;

- selected: 當marker被激活, 即用戶點擊的時候, 會更新x屬性值;

// Marker.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
// this property indicates whether this marker item
// is the current active one. Initially it is set to false
property bool active:  false
 
// creating the two states representing the respective
// set of property changes
states: [
     // the hovered state is set when the user has
     // the mouse hovering the marker item.
     State {
         name:  "hovered"
         // this condition makes this state active
         when: mouseArea.containsMouse && !root.active
         PropertyChanges { target: root; x: 5 }
     },
     State {
         name:  "selected"
         when: root.active
         PropertyChanges { target: root; x: 20 }
     }
]
 
// list of transitions that apply when the state changes
transitions: [
     Transition {
         to:  "hovered"
         NumberAnimation { target: root; property:  "x" ; duration: 300 }
     },
     Transition {
         to:  "selected"
         NumberAnimation { target: root; property:  "x" ; duration: 300 }
     },
     Transition {
         to:  ""
         NumberAnimation { target: root; property:  "x" ; duration: 300 }
     }
  ]

所以咱們聲明兩個state表明對應基於用戶行爲的屬性變化; 每個state綁定到when屬性所描寫敘述的狀況中;

Note 對於MouseArea的containsMouse屬性, hoverEnabled屬性必須設爲true;

Transition是用來在state以前切換時定義item的行爲的; 咱們可以在state激活的時候, 給變化的屬性定義各類動畫

Note item的默認state是一個空的string--("");

在MarkerPanel組件裏, 咱們在它被點擊的時候必須設置avtive屬性爲true; 參考MarkerPanel.qml;

5.2.2 給PagePanel加入Transition

在PagePanel組件, 咱們使用state來管理page之間的navigation(導航); 加入transition是天然的思路; 由於咱們在每個state裏面改變了opacity屬性, 可以加入給所有的state加入Transition來依據opacity的值執行NumberAnimation, 建立fade-in和fade-out效果;

// PagePanel.qml

1
2
3
4
5
6
7
8
9
10
//...
     // creating a list of transitions for
     // the different states of the PagePanel
     transitions: [
     Transition {
         // run the same transition for all states
         from:  "" ; to:  "*"
         NumberAnimation { property:  "opacity" ; duration: 500 }
     }
     ]

Note 一個item的opacity值也會被傳播(propagated)到它的child元素;

下一步

進一步改進UI;

---5End---


CHAPTER6 不少其它改進

這一階段, 可以以爲NoteApp的特性完畢, UI也符合需求; 只是, 這裏還有很是大空間改進, 儘管不是最重要(minor)但可以使得程序更美麗, 以及準備部署(deployment);

這一章有些小的改進, 也有些新的主意和特性加入進來; 固然, 咱們可以鼓舞繼續開發NoteApp, 也許又一次設計整個UI, 引入不少其它特性;

這裏有一個要點列表:

- 不少其它Javascript用來加入功能;

- 使用QML Item的z ordering(z軸次序);

- 使用本身定義的本地字體;

6.1 增強Note Item的功能

一個巧妙的(nifty)功能可以讓note依據輸入的text而變長; 爲了簡化, 當不少其它text輸入時, 它會將text摺疊(wrap)起來, 適應note寬度, 在垂直方向上將note變長; 

Text類型有一個paintedHeight屬性給出text在屏幕上繪製時的確切高度; 依據這個值, 咱們可以添加或下降note自身的高度; 

先定義一個Javascript helper方法, 爲Item計算height屬性的值, 它會放在Note組件的頂層;

// Note.qml

1
2
3
4
5
6
7
8
9
10
// JavaScript helper function that calculates the height of
// the note as more text is entered or removed.
function  updateNoteHeight() {
     // a note should have a minimum height
     var  noteMinHeight = 200
     var  currentHeight = editArea.paintedHeight + toolbar.height +40
     root.height = noteMinHeight
     if (currentHeight >= noteMinHeight)
         root.height = currentHeight
}

由於 updateNoteHeight()方法會依據editArea的paintedHeight屬性更新root的height, 咱們需要在paintedHeight變化的時候調用這種方法;

// Note.qml

1
2
3
4
5
6
7
8
TextEdit {
     id: editArea
     //...
     // called when the painterHeight property changes
     // then the note height has to be updated based
     // on the text input
     onPaintedHeightChanged: updateNoteHeight()
//...

Note 每個屬性有一個notifier signal(通知信號), 每次property改變的時候會被髮送(emit);

updateNoteHeight() JS方法會改變height屬性, 因此咱們可以使用Behavior定義一個行爲;

// Note.qml

1
2
3
4
//...
// defining a behavior when the height property changes
// for the root element
Behavior on height { NumberAnimation {} }

下一步

展現怎樣使用Item的z屬性來正確地爲note排序;


6.2 Note排序

Page裏面的Note沒法知道用戶當前正在使用哪個note; 默認狀況, 所有建立出來的Note item都有同樣的z屬性默認值, 這樣的狀況下, QML爲item建立默認的棧次序, 依賴於哪個item先被建立;

所需要的行爲應該是依據用戶交互來改變note的次序; 當用戶點擊note toolbar, 或者開始編輯note的時候, 當前的note應該跑到前面而不是躲在其它note如下; 這可以經過改變z值來作到, z值比其它的note高就能夠;

// Note.qml

1
2
3
//...
// setting the z order to 1 if the text area has the focus
z: editArea.activeFocus ?

1:0

activeFocus屬性在editArea有輸入焦點(input focus)的時候變爲true; 所以讓z屬性依賴editArea的activeFocus會比較安全; 而後, 咱們需要保證editArea在用戶點擊toolbar的時候有輸入焦點; 咱們在NoteToolbar組件中定義一個pressed()信號, 在鼠標press的時候發送; 

// NoteToolbar.qml

1
2
3
4
5
6
7
8
9
10
11
//...
     // this signal is emitted when the toolbar is pressed by the user
     signal pressed()
 
     // creating a MouseArea item
     MouseArea {
         id: mousearea
//...
         // emitting the pressed() signal on a mouse press event
         onPressed: root.pressed()
     }

在MouseArea中的onPressed信號handler中, 咱們發送NoteToolbar(root)的pressed()信號;

NoteToolbar的pressed()信號在Note組件中會處理;

// Note.qml

1
2
3
4
5
6
     // creating a NoteToolbar that will be anchored to its parent
     NoteToolbar {
         id: toolbar
         // setting the focus on the text area when the toolbar is pressed
         onPressed: editArea.focus =  true
//...

上面代碼中, 咱們將editArea的focus屬性設爲true, editArea會接收到輸入焦點; 這樣activeFocus屬性變爲true, 觸發(trigger)z屬性值的變化;

下一步

如何載入以及使用一個本身定義的本地font(字體)文件;


6.3 載入本身定義字體

在現代程序中使用部署一個本身定義的font而不依賴系統font是非常常見的方式; 對於NoteApp, 咱們想要用QML提供的方法作一樣的事情;

FontLoader類型讓你可以經過名字或URL路徑來載入font; 因爲載入的font可以被整個程序普遍使用, 咱們建議在main.qml中載入它, 而後在其它組件中使用;

// main.qml

1
2
3
4
5
6
7
8
9
10
11
//...
     // creating a webfont property that holds the font
     // loading using FontLoader
     property  var  webfont: FontLoader {
         id: webfontloader
         source:  "fonts/juleeregular.ttf"
         onStatusChanged: {
         if  (webfontloader.status == FontLoader.Ready)
             console.log( "font----Loaded" )
         }
     }

這樣咱們已經爲window item建立了一個webfont屬性; 這個屬性可以安全地在其它的組件中使用, 可以在editArea的Note組件中使用;

// Note.qml

1
2
3
4
     TextEdit {
         id: editArea
         font.family: window.webfont.name; font.pointSize: 13
//...

要設置editArea的font, 使用font.family屬性; 經過window, 咱們使用它的webfont屬性來獲取font名字, 用來設置;

下一步

將NoteApp準備好部署的細節;

---6End---

---TBC---YCR---

相關文章
相關標籤/搜索