我遇到了一个令我难过的问题,我想知道是否有任何已知的解决方法。似乎在使用子类 QAbstractItemView 的 QTreeView 上执行选择可能非常非常慢。
例证;我修改了使用PySide安装的示例文件:
..\site-packages\PySide\examples\itemviews\simpletreemodel\simpletreemodel.py
有大约4000行数据项,高于原来的40行。
我还将树视图设置为QtGui.QAbstractItemView.SelectionMode.ExtendedSelection
。运行这会导致gui交互最好是缓慢而且当“大”选择时非常慢。这通过鼠标操作,键盘操作和脚本操作来实现。
关于后者,我在修改后的脚本中添加了view.selectAll()
并对其进行了分析,以显示选择所有项目需要约84秒。
我正在考虑禁用选择并编写自己的自定义 Selection_Manager ,看看我是否可以手动加速。有没有人对如何更快地制定标准工作流程有任何其他建议或想法/例子?
提前致谢。
这是展示此问题的脚本。
from PySide import QtCore, QtGui
NUMBER_OF_ITEMS = 1000
NUMBER_OF_CHILDREN_PER_ITEM = 4
class TreeItem(object):
def __init__(self, data, parent=None):
self.parentItem = parent
self.itemData = data
self.childItems = []
def appendChild(self, item):
self.childItems.append(item)
def child(self, row):
return self.childItems[row]
def childCount(self):
return len(self.childItems)
def columnCount(self):
return len(self.itemData)
def data(self, column):
try:
return self.itemData[column]
except IndexError:
return None
def parent(self):
return self.parentItem
def row(self):
if self.parentItem:
return self.parentItem.childItems.index(self)
return 0
class TreeModel(QtCore.QAbstractItemModel):
def __init__(self, num=NUMBER_OF_ITEMS, num_of_children=NUMBER_OF_CHILDREN_PER_ITEM, parent=None):
super(TreeModel, self).__init__(parent)
self.rootItem = TreeItem( ["Title"] )
self.setupModelData( self.rootItem, num=num, num_of_children=num_of_children )
def columnCount(self, parent):
if parent.isValid():
return parent.internalPointer().columnCount()
else:
return self.rootItem.columnCount()
def data(self, index, role):
if not index.isValid():
return None
if role != QtCore.Qt.DisplayRole:
return None
item = index.internalPointer()
return item.data(index.column())
def flags(self, index):
if not index.isValid():
return QtCore.Qt.NoItemFlags
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def headerData(self, section, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self.rootItem.data(section)
return None
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
childItem = parentItem.child(row)
if childItem:
return self.createIndex(row, column, childItem)
else:
return QtCore.QModelIndex()
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
childItem = index.internalPointer()
parentItem = childItem.parent()
if parentItem == self.rootItem:
return QtCore.QModelIndex()
return self.createIndex(parentItem.row(), 0, parentItem)
def rowCount(self, parent):
if parent.column() > 0:
return 0
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
return parentItem.childCount()
def setupModelData(self, parent, num=100, num_of_children=10, ):
"""
Simple test method to fill the Model with items.
"""
for i in range( num ):
data = [ '{0}.Item'.format( i ) ]
item = TreeItem( data, parent )
parent.appendChild( item )
for j in range( num_of_children ):
data = [ '{0}.{1}.Child'.format( i, j ) ]
child = TreeItem( data, item )
item.appendChild( child )
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
model = TreeModel( num=NUMBER_OF_ITEMS, num_of_children=NUMBER_OF_CHILDREN_PER_ITEM )
view = QtGui.QTreeView()
view.setModel(model)
view.setSelectionMode( QtGui.QAbstractItemView.SelectionMode.ExtendedSelection )
view.expandAll()
view.setWindowTitle("Simple Tree Model")
view.show()
view.selectAll()
sys.exit(app.exec_())
答案 0 :(得分:1)
我使用Python profiler运行您的示例。总共需要大约86秒,其中51个是index
list
个Wed Dec 23 12:30:15 2015 stats.dat
21859835 function calls (21859832 primitive calls) in 86.528 seconds
Ordered by: internal time
List reduced from 110 to 10 due to restriction <10>
ncalls tottime percall cumtime percall filename:lineno(function)
3038204 51.266 0.000 51.266 0.000 {method 'index' of 'list' objects}
1 14.445 14.445 84.939 84.939 {exec_}
3053317 9.016 0.000 70.051 0.000 main.py:94(parent)
3060258 4.769 0.000 4.769 0.000 {method 'createIndex' of 'PySide.QtCore.QAbstractItemModel' objects}
3038204 2.383 0.000 53.649 0.000 main.py:36(row)
3168995 1.409 0.000 1.409 0.000 {method 'isValid' of 'PySide.QtCore.QModelIndex' objects}
1 1.187 1.187 86.528 86.528 main.py:2(<module>)
3053317 0.776 0.000 0.776 0.000 main.py:33(parent)
3121496 0.525 0.000 0.525 0.000 {method 'internalPointer' of 'PySide.QtCore.QModelIndex' objects}
22054 0.189 0.000 0.333 0.000 {method 'hasIndex' of 'PySide.QtCore.QAbstractItemModel' objects}
方法的花费。以下是分析器的输出,按时间排序,并以10个功能切断。
list.index()
在TreeItem.row()
中调用TreeItem
方法。因此,要确定项目的行号,将遍历其父项的子项列表,直到找到该项目。这总是让我觉得效率低下但到目前为止我从未遇到过性能问题。
因此,似乎可能的优化是将行号存储在row
中。这当然会使用额外的内存,如果更新树,则需要确保它保持一致。
另一个角度是研究为什么经常调用index
和QTreeView.selectAll
函数(大约三百万次,这对我来说似乎很多)。也许您可以通过查看var a: String!
的Qt源代码找到答案。
祝您好运,如果您找到解决方案,请告诉我。
P.S。我过去手工制作过QItemSelection,请参阅this post。我认为它不会对你有所帮助。
答案 1 :(得分:1)
我最终做了我的建议,禁用了QTreeView中的选择并自行管理选择。选择所有时间下降到大约2秒。仍然很高,但更好。
my_qtreeview.setSelectionMode( QtGui.QAbstractItemView.SelectionMode.NoSelection )
我的完整解决方案不会被分解和模块化,以便在此处发布任何可立即重复使用的内容,但也许这个代码段对任何感兴趣的人都有帮助。
一些注意事项:
这是我的核心代码示例,应该传达我正在做的事情的要点:
def on_tree_clicked_left( self, index_start, index_end, keyboard_modifiers ):
"""
:param index_start: QtCore.QModelIndex
:param index_end: QtCore.QModelIndex
:param keyboard_modifiers: QtGui.QApplication.keyboardModifiers
"""
if ( index_start is None and index_end is None ) or \
not index_start.isValid() or not index_end.isValid():
self.model_data_objs.deselect_all()
return None
if index_start is None:
index_start = index_end
start_node = index_start.model().mapToSource( index_start ).internalPointer()
end_node = index_end.model().mapToSource( index_end ).internalPointer()
# get the nodes to operate on ...
nodes = [ start_node ]
if not index_start == index_end:
nodes_above = list()
nodes_below = list()
index_above = self.gui_tree.indexAbove( index_start )
index_below = self.gui_tree.indexBelow( index_start )
while not end_node in nodes_above and not end_node in nodes_below:
if index_above.isValid():
node_after = index_above.model().mapToSource( index_above ).internalPointer()
nodes_above.append( node_after )
index_above = self.gui_tree.indexAbove( index_above )
if index_below.isValid():
node_before = index_below.model().mapToSource( index_below ).internalPointer()
nodes_below.append( node_before )
index_below = self.gui_tree.indexBelow( index_below )
if end_node in nodes_above:
nodes += nodes_above
if end_node in nodes_below:
nodes += nodes_below
# get the operation, based on index_start column ...
clicked_column = index_start.column()
if not clicked_column == 0:
attr = COLUMNS[ clicked_column ].get( 'attr_name' )
else:
attr = 'selected'
if not keyboard_modifiers == QtCore.Qt.ControlModifier and \
not keyboard_modifiers == QtCore.Qt.AltModifier:
self.model_data_objs.deselect_all()
# perform the operation
new_value = not getattr( start_node, attr )
for node in nodes:
setattr( node, attr, new_value )