2017-02-15 139 views
0

我有一个TableView动态填充ListModel,我需要在“QML端”进行排序,最好不要替换列表中的任何元素,因为有相当多的逻辑连接到几个的表格信号(包括一些定制的)。ListModel.move()极其缓慢

我的问题是,当表增长超过〜1K元件构成,元件的移动简单地采取一个不合理长的时间(见下面的代码)。将排序放在WorkerScript中对于改进用户体验几乎没有作用,因为如果没有发生〜0.5秒的事情,用户往往只是一次又一次地点击。所以我想知道的是,如果有人知道一种方法来提高ListModel.move()的性能,暂时抑制信号,或者有另一种解决方案呢?

此致

拉格纳

示例代码:

import QtQuick 2.7 
import QtQuick.Layouts 1.3 
import QtQuick.Controls 1.4 

ColumnLayout { 
    width: 400 

    TableView { 
     id: table 
     Layout.fillHeight: true 
     Layout.fillWidth: true 
     model: ListModel { dynamicRoles: false } 
     onSortIndicatorColumnChanged: sort(); 
     onSortIndicatorOrderChanged: sort(); 

     TableViewColumn { 
      role: "num" 
      title: "Numeric column" 
      width: table.contentItem.width/3 
     } 
     TableViewColumn { 
      role: "str" 
      title: "Text column" 
      width: table.contentItem.width * 2/3 
     } 

     // functionality 
     function sort() { 
      if(model.count < 2) { 
       console.log("No need to sort."); 
       return true; 
      } 
      var r = getColumn(sortIndicatorColumn).role; 
      var type = typeof(model.get(0)[r]); 
      if(type != "string" && type != "number") { 
       console.log("Unable to sort on selected column."); 
       return false; 
      } 
      switch(sortMethod.currentIndex) { 
       case 0: var sortFunc = _sortMoveWhileNoCache; break; 
       case 1: sortFunc = _sortMoveWhile; break; 
       case 2: sortFunc = _sortMoveAfter; break; 
       case 3: sortFunc = _sortSetAfter; break; 
       case 4: sortFunc = _sortAppendRemoveAfter; break; 
       default: 
        console.log("Unknown sort method."); 
        return false; 
      } 
      console.time(sortFunc.name); 
      sortFunc(r); 
      console.timeEnd(sortFunc.name); 
      return true; 
     } 

     // invokers 
     function _sortMoveWhileNoCache(r) { 
      console.time("sortMove"); 
      _qsortMoveNoCache(r, 0, model.count-1); 
      console.timeEnd("sortMove"); 
     } 
     function _sortMoveWhile(r) { 
      console.time("setUp"); 
      var arr = []; 
      for(var i = model.count-1; i > -1; i--) arr[i] = model.get(i)[r]; 
      console.timeEnd("setUp"); 
      console.time("sortMove"); 
      _qsortMove(arr, 0, arr.length-1); 
      console.timeEnd("sortMove"); 
     } 
     function _sortMoveAfter(r) { 
      console.time("setUp"); 
      var arr = []; 
      arr[0] = { "val": model.get(0)[r], "oldIdx": 0, "oldPrev": null }; 
      for(var i = 1; i < model.count; i++) { 
       arr[i] = { "val": model.get(i)[r], 
          "oldIdx": i, 
          "oldPrev": arr[i-1] }; 
      } 
      console.timeEnd("setUp"); 
      console.time("sort"); 
      _qsortVal(arr, 0, arr.length-1); 
      console.timeEnd("sort"); 
      console.time("move"); 
      for(i = 0; i < arr.length; i++) { 
       if(arr[i].oldIdx !== i) { 
        model.move(arr[i].oldIdx, i, 1); 
        for(var prev = arr[i].oldPrev; 
         prev !== null && prev.oldIdx >= i; 
         prev = prev.oldPrev) 
         prev.oldIdx++; 
       } 
      } 
      console.timeEnd("move"); 
     } 
     function _sortSetAfter(r) { 
      console.time("setUp"); 
      var arr = [], tmp = []; 
      for(var i = model.count-1; i > -1; i--) { 
       var lmnt = model.get(i); 
       // shallow clone 
       tmp[i] = Object.create(lmnt); 
       for(var p in lmnt) tmp[i][p] = lmnt[p]; 
       arr[i] = { "val": tmp[i][r], "oldIdx": i }; 
      } 
      console.timeEnd("setUp"); 
      console.time("sort"); 
      _qsortVal(arr, 0, arr.length-1); 
      console.timeEnd("sort"); 
      console.time("set"); 
      // set()ing invalidates get()ed objects, hence the cloning above 
      for(i = 0; i < arr.length; i++) model.set(i, tmp[arr[i].oldIdx]); 
      console.timeEnd("set"); 
      delete(tmp); 
     } 
     function _sortAppendRemoveAfter(r) { 
      console.time("setUp"); 
      var arr = [], tmp = []; 
      for(var i = model.count-1; i > -1; i--) { 
       tmp[i] = model.get(i); 
       arr[i] = { "val": tmp[i][r], "oldIdx": i }; 
      } 
      console.timeEnd("setUp"); 
      console.time("sort"); 
      _qsortVal(arr, 0, arr.length-1); 
      console.timeEnd("sort"); 
      console.time("appendRemove"); 
      // append()ing does not, on win10 x64 mingw, invalidate 
      for(i = 0; i < arr.length; i++) model.append(tmp[arr[i].oldIdx]); 
      model.remove(0, arr.length); 
      console.timeEnd("appendRemove"); 
     } 

     // sorting functions 
     function _qsortMoveNoCache(r, s, e) { 
      var i = s, j = e, piv = model.get(Math.floor((s+e)/2))[r]; 
      while(i < j) { 
       if(sortIndicatorOrder == Qt.AscendingOrder) { 
        for(; model.get(i)[r] < piv; i++){} 
        for(; model.get(j)[r] > piv; j--){} 
       } else { 
        for(; model.get(i)[r] > piv; i++){} 
        for(; model.get(j)[r] < piv; j--){} 
       } 
       if(i <= j) { 
        if(i !== j) { 
         model.move(i, j, 1); 
         model.move(j-1, i, 1); 
        } 
        i++; 
        j--; 
       } 
      } 
      if(s < j) _qsortMoveNoCache(r, s, j); 
      if(i < e) _qsortMoveNoCache(r, i, e); 
     } 
     function _qsortMove(arr, s, e) { 
      var i = s, j = e, piv = arr[Math.floor((s+e)/2)]; 
      while(i < j) { 
       if(sortIndicatorOrder == Qt.AscendingOrder) { 
        for(; arr[i] < piv; i++){} 
        for(; arr[j] > piv; j--){} 
       } else { 
        for(; arr[i] > piv; i++){} 
        for(; arr[j] < piv; j--){} 
       } 
       if(i <= j) { 
        if(i !== j) { 
         model.move(i, j, 1); 
         model.move(j-1, i, 1); 
         var tmp = arr[i]; 
         arr[i] = arr[j]; 
         arr[j] = tmp; 
        } 
        i++; 
        j--; 
       } 
      } 
      if(s < j) _qsortMove(arr, s, j); 
      if(i < e) _qsortMove(arr, i, e); 
     } 
     function _qsortVal(arr, s, e) { 
      var i = s, j = e, piv = arr[Math.floor((s+e)/2)].val; 
      while(i < j) { 
       if(sortIndicatorOrder == Qt.AscendingOrder) { 
        for(; arr[i].val < piv; i++){} 
        for(; arr[j].val > piv; j--){} 
       } else { 
        for(; arr[i].val > piv; i++){} 
        for(; arr[j].val < piv; j--){} 
       } 
       if(i <= j) { 
        if(i !== j) { 
         var tmp = arr[i]; 
         arr[i] = arr[j]; 
         arr[j] = tmp; 
        } 
        i++; 
        j--; 
       } 
      } 
      if(s < j) _qsortVal(arr, s, j); 
      if(i < e) _qsortVal(arr, i, e); 
     } 
    } 

    RowLayout { 
     Button { 
      Layout.fillWidth: true 
      text: "Add 1000 elements (" + table.model.count + ")" 
      onClicked: { 
       var chars = " abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ"; 
       for(var i = 0; i < 1000; i++) { 
        var str = ""; 
        for(var j = 0; j < Math.floor(Math.random()*20)+1; j++) 
         str += chars[Math.floor(Math.random()*chars.length)]; 
        table.model.append({ "num": Math.round(Math.random()*65536), 
             "str": str }); 
       } 
      } 
     } 
     Button { 
      text: "Clear list model" 
      onClicked: table.model.clear(); 
     } 
     ComboBox { 
      id: sortMethod 
      Layout.fillWidth: true 
      editable: false 
      model: ListModel { 
       ListElement { text: "Move while sorting, no cache" } 
       ListElement { text: "Move while sorting" } 
       ListElement { text: "Move after sorting" } 
       ListElement { text: "Set after sorting" } 
       ListElement { text: "Append and remove after sorting" } 
      } 
     } 
    } 
} 

当运行上述使用Qt-win10-x64的mingw的,5K元件,清除在每个分选方法之间的一览我得到以下结果(_sortSetAfter比_sortMoveWhile [NoCache]快20倍)

// num 
sortMove: 3224ms 
_sortMoveWhileNoCache: 3224ms 
// str 
sortMove: 3392ms 
_sortMoveWhileNoCache: 3392ms 

// num 
setUp: 20ms 
sortMove: 4684ms 
_sortMoveWhile: 4704ms 
// str 
setUp: 16ms 
sortMove: 3421ms 
_sortMoveWhile: 3437ms 

// num 
setUp: 18ms 
sort: 15ms 
move: 4985ms 
_sortMoveAfter: 5018ms 
// str 
setUp: 8ms 
sort: 20ms 
move: 5200ms 
_sortMoveAfter: 5228ms 

// num 
setUp: 116ms 
sort: 21ms 
set: 27ms 
_sortSetAfter: 164ms 
// str 
setUp: 63ms 
sort: 26ms 
set: 25ms 
_sortSetAfter: 114ms 

// num 
setUp: 20ms 
sort: 19ms 
appendRemove: 288ms 
_sortAppendRemoveAfter: 328ms 
// str 
setUp: 22ms 
sort: 26ms 
appendRemove: 320ms 
_sortAppendRemoveAfter: 368ms 
+0

任何具体的原因,你在C++做数据吨maipulation的JavaScript中,而不是这样做呢? –

+3

我建议使用自定义ListModel的(甚至在C++中,如果你需要的性能),从您的视图分离的模型,而不更新UI进行排序,然后除非你想看中排序动画再次附上模型..,但这会导致大量的性能表现。 :) – xander

+2

我建议使用这个库:https://github.com/oKcerG/SortFilterProxyModel/。它允许您从QML实例化和配置代理模型,但是在C++中实现。免责声明:我是它的创建者 – GrecKo

回答

2

虽然我凯文·克拉姆Xander的同意,你有多种方式来surpress绑定。

要么你可以用signal.connect(slotToConnect)约束他们,并与signal.disconnect(slotToDisconnect)直接断开连接,或者你使用Connections与您更改uppon启动和分拣完成一个enabled - 值。

此外,您应该考虑在某些操作的执行时间超过几个时显示一些BusyIndicatorms

但我必须承认,我看不到任何理由为JS

+1

所以你可以看到一个原因? (: – GrecKo

+0

* negative concord * – derM

+0

将信号处理程序移动到Connections听起来非常有希望,但会给出一个尝试。至于JS部分,正如我在上面的注释中提到的,QML前端只是其中一个,并且发送数据在后端和前端之间_may_会更费时间 – Ragnar