なんか変な位置に挿入される現象の対応
これは僕が実際に動かして感じた動作なので具体的な実装とは合ってない可能性が非常に高いです。
なにが起こったか。
1.UIスレッドからListViewへデータを追加。
(2行追加した。)

2.WorkerScriptを使って裏スレッドからListViewへデータを追加
(4行分追加した。ココまでは正常。)

3.もう一度、UIスレッドからデータ追加
(パッと見は正常。)

4.もう一度、裏スレッドからデータを追加
(3で追加したデータの前に挿入されたように見える。)

5.一回スクロールアウト
(リストが再構成されて3で追加したデータが無かったことになってる。)

初めて裏スレッドでModelにアクセスした時点で裏用のデータのコピーが作成されてsync()をする度に表にプッシュされてくる感じ。
その際、表での操作を全く無視してくるのでUI側の操作は消える。
/// まとめると ///
・ListModelの中に表と裏でダブルバッファリングしているイメージ。
(初めてWorkerScriptでアクセスした時点でコピーっぽい)
・UIからアクセスできるデータとスレッドからアクセスできるデータに分かれてる。
・sync()をすると裏のデータが表にプッシュされる。
・sync()の動作は一方通行で表側の変更を裏に動機をかけることはできないっぽい。
・プッシュされるとき、UIから操作した内容はなかったことにされる。
ただし、最初に裏スレッドを動かす以前のものは残る。
/// 対策 ///
・常に裏スレッドから操作する。。。
ということで、WorderScriptを使ったListModelへのアクセス用エレメントを作って見ました。
サンプルを今回の現象とラッパーを見れるように作りました。
基本は前回の「QML(Qt)でマルチスレッド処理」です。
余談ですが、
ListModelの中にWorkerScriptを置こうとすると、「ListElement: cannot contain nested elements 」とエラーが表示されてすでに内部に持っている実装になっている様子。
これは僕が実際に動かして感じた動作なので具体的な実装とは合ってない可能性が非常に高いです。
なにが起こったか。
1.UIスレッドからListViewへデータを追加。
(2行追加した。)
2.WorkerScriptを使って裏スレッドからListViewへデータを追加
(4行分追加した。ココまでは正常。)
3.もう一度、UIスレッドからデータ追加
(パッと見は正常。)
4.もう一度、裏スレッドからデータを追加
(3で追加したデータの前に挿入されたように見える。)
5.一回スクロールアウト
(リストが再構成されて3で追加したデータが無かったことになってる。)
初めて裏スレッドでModelにアクセスした時点で裏用のデータのコピーが作成されてsync()をする度に表にプッシュされてくる感じ。
その際、表での操作を全く無視してくるのでUI側の操作は消える。
/// まとめると ///
・ListModelの中に表と裏でダブルバッファリングしているイメージ。
(初めてWorkerScriptでアクセスした時点でコピーっぽい)
・UIからアクセスできるデータとスレッドからアクセスできるデータに分かれてる。
・sync()をすると裏のデータが表にプッシュされる。
・sync()の動作は一方通行で表側の変更を裏に動機をかけることはできないっぽい。
・プッシュされるとき、UIから操作した内容はなかったことにされる。
ただし、最初に裏スレッドを動かす以前のものは残る。
/// 対策 ///
・常に裏スレッドから操作する。。。
ということで、WorderScriptを使ったListModelへのアクセス用エレメントを作って見ました。
サンプルを今回の現象とラッパーを見れるように作りました。
基本は前回の「QML(Qt)でマルチスレッド処理」です。
余談ですが、
ListModelの中にWorkerScriptを置こうとすると、「ListElement: cannot contain nested elements 」とエラーが表示されてすでに内部に持っている実装になっている様子。
サンプルメインのQML
WorderScriptから使用するスクリプト
常にスレッドから操作するためのラッパーエレメント
常にスレッドから操作するためのラッパーエレメント用のスレッドスクリプト
<Category : Qt>
import QtQuick 1.0
Rectangle {
id: _root
width: 300
height: 360
// ボタン
Row{
id: _btnArea
anchors.top: parent.top
width: parent.width
// WorkerScript1個目から追加
Rectangle{
width: parent.width / parent.children.length
height: _lavel.paintedHeight * 2
color: "#ddddff"
border.color: "#555555"
border.width: 1
Text{
id: _lavel
anchors.centerIn: parent
text: "from Back 1"
}
MouseArea{
anchors.fill: parent
onClicked: {
console.debug("from background");
// スレッド開始
_thread1.sendMessage({"model":_list.model
, "id": 1
, "number": 2
, "string": "Start!"
});
}
}
}
// WorkerScript2個目から追加
Rectangle{
width: parent.width / parent.children.length
height: _lavel2.paintedHeight * 2
color: "#ddddff"
border.color: "#555555"
border.width: 1
Text{
id: _lavel2
anchors.centerIn: parent
text: "from Back 2"
}
MouseArea{
anchors.fill: parent
onClicked: {
console.debug("from Background 2");
// スレッド開始
_thread2.sendMessage({"model": _list.model
, "id": 2
, "number": 2
, "string": "set null"
});
}
}
}
// ラッパーエレメント使ってスレッドから追加
Rectangle{
width: parent.width / parent.children.length
height: _lavel3.paintedHeight * 2
color: "#ddddff"
border.color: "#555555"
border.width: 1
Text{
id: _lavel3
anchors.centerIn: parent
text: "from Back 3"
}
MouseArea{
anchors.fill: parent
onClicked: {
console.debug("from Background 3");
// 追加
_listManager.append({"_message": "3 : x : "
+ Qt.formatDateTime(new Date(), "yyyy/MM/dd hh:mm:ss")});
}
}
}
// UIスレッドから追加するボタン
Rectangle{
width: parent.width / parent.children.length
height: _lavel4.paintedHeight * 2
color: "#ddddff"
border.color: "#555555"
border.width: 1
Text{
id: _lavel4
anchors.centerIn: parent
text: "from UI 2"
}
MouseArea{
anchors.fill: parent
onClicked: {
console.debug("from UI 2");
// 追加
_model.append({"_message": "UI 2 : "
+ Qt.formatDateTime(new Date(), "yyyy/MM/dd hh:mm:ss")});
}
}
}
}
// リストビューのデータ操作用エレメント
ListModelManager{
id: _listManager
model: _list.model
}
// スレッド用エレメント
WorkerScript {
id: _thread1
source: "script.js" // スレッド処理をするスクリプトファイルの指定
onMessage: {
// Workerスレッドからの通信イベント
console.debug("finish thread 1:" + messageObject.result
+ ", split=" + messageObject.split);
}
}
// スレッド用エレメント
WorkerScript {
id: _thread2
source: "script.js" // スレッド処理をするスクリプトファイルの指定
onMessage: {
// Workerスレッドからの通信イベント
console.debug("finish thread 2:" + messageObject.result
+ ", split=" + messageObject.split);
}
}
// リストモデル
ListModel{
id: _model
}
// リスト用レイアウト
Component{
id: _delegate
Rectangle {
width: _root.width
height: _text.paintedHeight * 2
border.color: "#dddddd"
border.width: 1
Text{
id: _text
anchors.centerIn: parent
text: _message
}
}
}
// リスト
ListView{
id: _list
anchors.top: _btnArea.bottom
anchors.left: _root.left
anchors.right: _root.right
anchors.bottom: _root.bottom
clip: true
model: _model
delegate: _delegate
onCountChanged: {
// 自動スクロール
// positionViewAtEnd(); // コレ使うなら1.1に変更
}
}
}
WorderScriptから使用するスクリプト
// scripts.js
WorkerScript.onMessage = function(message) {
console.debug("start worker script:" + message.number
+ "," + message.string);
if(message.model === null){
// nop
}else{
var now = 0;
var split = new Date();
message.model.sync();
for(var i=0; i<message.number; i++){
delay(1000);
now = new Date();
message.model.append({"_message": message.id + " : "
+ i + " : "
+ Qt.formatDateTime(now, "yyyy/MM/dd hh:mm:ss")});
message.model.sync();
}
split = (new Date()) - split;
}
// メインスレッドへの応答
WorkerScript.sendMessage({ "result": "tRue", "split": split })
}
// ウエイトを入れる
function delay(msec){
var start = 0;
start = new Date();
do{
now = new Date();
}while((now - start) < msec);
}
常にスレッドから操作するためのラッパーエレメント
import QtQuick 1.0
WorkerScript {
id: _root
source: "./ListModelManagerScript.js" // スレッド処理をするスクリプトファイルの指定
property variant model: null
property int count: (model === null) ? 0 : model.count
// Workerスレッドからの通信イベント
onMessage: {
console.debug("finish thread : " + messageObject.result);
}
// データチェック
function isOk(object){
if((model === null) || (object === null)
|| (object === undefined)){
return false;
}else{
return true;
}
}
// 追加する
function append(object){
if(!isOk(object)){
// nop
}else{
sendMessage({"model": model
, "type": "append"
, "object": object
});
}
}
// クリアする
function clear(){
if(!isOk(true)){
// nop
}else{
sendMessage({"model": model
, "type": "clear"
});
}
}
// 取得する
function get(index){
if(!isOk(true)){
return null;
}else{
return model.get(index);
}
}
// 挿入する
function insert(index, object){
if(!isOk(object)){
// nop
}else{
sendMessage({"model": model
, "type": "insert"
, "index": index
, "object": object
});
}
}
// 移動する
function move(from, to, n){
if(!isOk(true)){
// nop
}else{
model.move(from, to, n);
// sendMessage({"model": model
// , "type": "move"
// , "from": from
// , "to": to
// , "n": n
// });
}
}
// 内容を変更する
function set(index, object){
if(!isOk(object)){
// nop
}else{
sendMessage({"model": model
, "type": "set"
, "index": index
, "object": object
});
}
}
// プロパティの変更
function setProperty(index, property, value){
if(!isOk(value)){
// nop
}else{
sendMessage({"model": model
, "type": "setProperty"
, "index": index
, "property": property
, "value": value
});
}
}
// 同期
function sync(){
sendMessage({"model": model, "type": "sync"});
}
}
常にスレッドから操作するためのラッパーエレメント用のスレッドスクリプト
WorkerScript.onMessage = function(message) {
console.debug("start worker script");
if(message.model === null){
// nop
}else{
switch(message.type){
case "append":
message.model.append(message.object);
break;
case "clear":
message.model.clear();
break;
case "insert":
message.model.insert(message.index, message.object);
break;
case "move":
message.model.move(message.from, message.to, message.n);
break;
case "set":
message.model.set(message.index, message.object);
break;
case "setProperty":
message.model.setProperty(message.index, message.property, message.value);
break;
default:
break;
}
message.model.sync();
}
// メインスレッドへの応答
WorkerScript.sendMessage({ "result": "true" })
}

