在学习了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线程的阻塞,如果不能及时响应用户的操作,甚至出现假死的现象,对于应用开发都是大忌。