在學習了MouseArea、Text、Image這些基本組件後,我們這一章學習如何在QML中完成一些異步處理。
這一章我們通過下述兩個例子來分別講解一下Timer和WorkerScript的使用。
threadedlistmodel/timedisplay.qml
這一個示例的原始代碼中同時使用了Timer和WorkerScript來完成一個比較簡單的工作,爲了簡化處理,我針對代碼做了一些小改動,去掉了WorkerScript部分(同時包括dataloader.js部分),只使用Timer來完成是示例一樣的效果。修改後的代碼如下(原始代碼請參考Qt的示例代碼):
Rectangle {
color: "white"
width: 200
height: 300
ListView {
anchors.fill: parent
model: listModel
delegate: Component {
Text { text: time }
}
ListModel { id: listModel }
Timer {
id: timer
interval: 2000; repeat: true
running: true
triggeredOnStart: true
onTriggered: {
var data = {'time': new Date().toTimeString()};
listModel.append(data);
listModel.sync(); // updates the changes to the list
}
}
}
}
那麼從上面的代碼可以看到,ListView、ListModel都是我們之前已經瞭解的組件,那麼我們就把焦點放到Timer組件上。
Timer簡述
A Timer can be used to trigger an action either once, or repeatedly at a given interval.
其實Timer組件還是很好理解的,就是一個定時器,按照給定的interval定時觸發,我們只需要在onTriggered事件響應函數中完成自己需要的操作即可。
- interval指定了觸發的事件間隔是2s
- repeat和running都是true,表示該Timer循環進行觸發,而不是隻觸發一次
- triggeredOnStart表示上來就觸發一次,而不是等interval之後才觸發第一次
- onTriggered事件相應函數中,我們通過listModel的數據來刷新ListView中顯示
Timer的使用也就是上面的幾個屬性和事件通知了,示例中沒有使用到的還有幾個函數:restart()、start()和stop(),其意義和使用方法也很簡單。
workerscript/workerscript.qml
在上一個示例中,我們只關注了Timer,去掉了WorkerScript相關的代碼。在這一節中,我們就需要完全使用WorkerScript來完成我們的需求了。
先看代碼整體結構:
Rectangle {
width: 320; height: 480
//! [1]
WorkerScript {...}
//! [1]
Row {...}
Text {...}
Text {...}
}
其中兩個Text分別是顯示用戶提示("Pascal's Triangle Calculator")和計算結果(resultText),而Row部分則是水平排列的兩個Spinner。
這裏用的Spinner是一個自定義的Component,具體實現是在:/threading/workerscript/Spinner.qml文件中,這裏暫時先不詳細分析這個文件,我們先看一看如何使用Spinner:
Spinner {
id: rowSpinner
label: "Row"
onValueChanged: {
resultText.text = "Loading...";
myWorker.sendMessage( { row: rowSpinner.value, column: columnSpinner.value } );
}
}
注:另外一個Spinner除了id和label不同之外,onValueChanged事件響應函數是一模一樣的,此處不再重複貼上代碼。
從代碼上看,一個Spinner的屬性包括label(即顯示在Spinner上面的文字),一個value(即當前Spinner顯示的數據),如下圖所示:
當Spinner中的數據發送變化時,則會觸發onValueChanged事件相應函數。關於自定義組件Spinner的講解暫時先到這裏,後面再詳細展開。
WorkerScript簡述
Use WorkerScript to run operations in a new thread. This is useful for running operations in the background so that the main GUI thread is not blocked.
從上面官方說明上,我們可以瞭解WorkerScript就是設計成可以在後臺執行操作,而不會阻塞UI線程的一種組件。通過上面的說明,我們也可以瞭解WorkerScript必須有的幾個元素:
- source:既然是後臺執行操作,這裏使用了source指定一個js文件運行後臺的操作
- onMessage:既然是異步處理,那麼肯定涉及到的message,這裏的message是雙向的,從js文件到qml文件使用onMessage事件相應函數
- sendMessage(jsobject message):從qml到js文件,使用sendMessage函數發送給js文件需要執行的操作
下面就是代碼中的WorkerScript部分:
WorkerScript {
id: myWorker
source: "workerscript.js"
onMessage: {
if (messageObject.row == rowSpinner.value && messageObject.column == columnSpinner.value){ //Not an old result
if (messageObject.result == -1)
resultText.text = "Column must be <= Row";
else
resultText.text = messageObject.result;
}
}
}
而workerscript.js中的onMessage函數如下:
WorkerScript.onMessage = function(message) {
//Calculate result (may take a while, using a naive algorithm)
var calculatedResult = triangle(message.row, message.column);
//Send result back to main thread
WorkerScript.sendMessage( { row: message.row,
column: message.column,
result: calculatedResult} );
}
從上面代碼可以很明顯的看到:
從qml到js使用的是myWorker.sendMessage( { row: rowSpinner.value, column: columnSpinner.value } );,數據包含row和column,在workerscript.js中的onMessage函數中也是使用參數message的兩個屬性值;
從js到qml使用的是WorkerScript.sendMessage( { row: message.row,column: message.column,result: calculatedResult} );,數據包含row、colume和result,在qml的onMessage函數中也是使用了這三個參數,不過需要注意的是這裏的messageObject應該是內置變量(吐槽+1)。
總結
本節學到的知識點:
- Timer組件的使用方法
- WorkerScript組件的使用方法
- 學習如何通過Timer組件或WorkerScript組件來完成後臺線程執行操作,而在UI線程中刷新的方法
- 稍微瞭解如何在qml中如何使用js文件
從今天開始,我們開始了學習如何在qml中完成一些邏輯,也算是擺脫了只是學習UI組件的不爽。但是無論後臺邏輯完成什麼複雜的處理,絕不能造成UI線程的阻塞,如果不能及時響應用戶的操作,甚至出現假死的現象,對於應用開發都是大忌。