[PyQt] Performance of QTreeView
Knacktus
knacktus at googlemail.com
Sun Oct 10 10:08:51 BST 2010
import sys
import PyQt4.QtGui as QtGui
import PyQt4.QtCore as QtCore
#########################################################################
# Underlying data
# ----------------
# - RuntimeItems hold the data. They come from a database.
# - ViewItems are the objects, that are given to the model indexes of Qt.
# They are constructed according to some rules like filters and
# configuration.
# - DummieViewItemFactory processes the rules and configurations.
# The example here is simplfied. An instance of the factory is given
# to each ViewItem.
# The view item calls the
# DummieViewItemFactory.get_view_item_children method
# to request calculation of its children on demand.
# - For this demo-version, the number of items is controlled by
# DummieViewItemFactory.max_items. It's passed in by the constructor.
# - Nesting as high as possible: One child per parent.
#########################################################################
class RuntimeItem(object):
"""Represent the real world business items. These objects
have a lot of relations.
"""
def __init__(self, name, ident, item_type):
self.name = name
self.ident = ident
self.item_type = item_type
class ViewItem(object):
"""Represent items that are to be shown to the user in a QTreeView.
Those items do only occur one time in a view. They have a
corresponding runtime_item.
The children are calculated by the view_item_factory on demand.
"""
def __init__(self, view_item_factory, runtime_item=None, parent=None,
hidden_runtime_items=None):
self.view_item_factory = view_item_factory
self.runtime_item = runtime_item
self.parent = parent
self.hidden_runtime_items = hidden_runtime_items
@property
def children(self):
try:
return self._children
except AttributeError:
self._children = \
self.view_item_factory.get_view_item_children(self)
return self._children
@children.setter
def children(self, children):
self._children = children
class DummieViewItemFactory(object):
"""Creates the view_items. This is a dumb dummie as a simple
example. Normally a lot of things happen here like filtering
and configuration. But once the view_item hierachy is build,
this shouldn't be called at all.
"""
def __init__(self, runtime_item, max_items):
self.runtime_item = runtime_item
self.max_items = max_items
self.item_counter = 0
self.aux_root_view_item = ViewItem(self)
def get_view_item_children(self, view_item_parent):
if self.item_counter > self.max_items:
return []
self.item_counter += 1
view_item = ViewItem(self, self.runtime_item, view_item_parent)
return [view_item]
#########################################################################
# Qt classes
# ----------------
# - This should be standard stuff. I've got most of it from the Rapid
# GUI Programming book.
# - The ActiveColums class tells the model which colums to use.
# - The TreeView has a context menu with navigation actions.
# - The expand_all calls the Qt slot. Here the surprise for the
# performance.
#########################################################################
class ActiveColumns(object):
def __init__(self, columns):
self.columns = columns
class TreeView(QtGui.QTreeView):
def __init__(self, aux_root_view_item, active_columns, parent=None,
header_hidden=False):
super(TreeView, self).__init__(parent)
self.setIndentation(10)
self.active_columns = active_columns
self.setAlternatingRowColors(True)
self.setHeaderHidden(header_hidden)
self.setAllColumnsShowFocus(True)
self.setUniformRowHeights(True)
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
model = TreeModel(aux_root_view_item, self)
self.setModel(model)
e_a_action = QtGui.QAction("Expand all", self)
e_a_action.setToolTip("Expands all items of the tree.")
e_a_action.triggered.connect(self.expand_all)
e_a_b_action = QtGui.QAction("Expand all below", self)
e_a_b_action.setToolTip("Expands all items under the selection.")
e_a_b_action.triggered.connect(self.expand_all_below)
c_a_action = QtGui.QAction("Collapse all", self)
c_a_action.setToolTip("Collapses all items of the tree.")
c_a_action.triggered.connect(self.collapse_all)
c_a_b_action = QtGui.QAction("Collapse all below", self)
c_a_b_action.setToolTip("Collapses all items under the selection.")
c_a_b_action.triggered.connect(self.collapse_all_below)
for action in (e_a_action, c_a_action, e_a_b_action, c_a_b_action):
self.addAction(action)
def expand_all(self):
self.expandAll()
def collapse_all(self):
self.collapseAll()
def expand_all_below(self):
def expand_all_below_recursive(parent_index):
self.expand(parent_index)
children_indexes = \
self.tree_itemmodel.get_children_indexes(parent_index)
for child_index in children_indexes:
expand_all_below_recursive(child_index)
indexes = self.selectedIndexes()
if indexes:
index = indexes[0]
expand_all_below_recursive(index)
def collapse_all_below(self):
def collapse_all_below_recursive(parent_index):
self.collapse(parent_index)
children_indexes = \
self.tree_itemmodel.get_children_indexes(parent_index)
for child_index in children_indexes:
collapse_all_below_recursive(child_index)
indexes = self.selectedIndexes()
if indexes:
index = indexes[0]
collapse_all_below_recursive(index)
class TreeModel(QtCore.QAbstractItemModel):
def __init__(self, aux_root_view_item, parent):
super(TreeModel, self).__init__(parent)
self.aux_root_view_item = aux_root_view_item
self.active_columns = parent.active_columns
def rowCount(self, parent_index):
parent_view_item = self.view_item_from_index(parent_index)
if parent_view_item is None:
return 0
return len(parent_view_item.children)
def get_children_indexes(self, parent_index):
children_indexes = []
for row_no in range(self.rowCount(parent_index)):
children_indexes.append(self.index(row_no, 0, parent_index))
return children_indexes
def columnCount(self, parent):
return len(self.active_columns.columns)
def data(self, index, role):
if role == QtCore.Qt.TextAlignmentRole:
return int(QtCore.Qt.AlignTop|QtCore.Qt.AlignLeft)
if role != QtCore.Qt.DisplayRole:
return None
view_item = self.view_item_from_index(index)
try:
data = getattr(view_item.runtime_item,
self.active_columns.columns[index.column()])
except AttributeError:
data = ""
return data
def headerData(self, section, orientation, role):
if (orientation == QtCore.Qt.Horizontal and
role == QtCore.Qt.DisplayRole):
assert 0 <= section <= len(self.active_columns.columns)
return self.active_columns.columns[section]
return QtCore.QVariant()
def index(self, row, column, parent_index):
view_item_parent = self.view_item_from_index(parent_index)
return self.createIndex(row, column,
view_item_parent.children[row])
def parent(self, child_index):
child_view_item = self.view_item_from_index(child_index)
if child_view_item is None:
return QtCore.QModelIndex()
parent_view_item = child_view_item.parent
if parent_view_item is None:
return QtCore.QModelIndex()
grandparent_view_item = parent_view_item.parent
if grandparent_view_item is None:
return QtCore.QModelIndex()
grandparent_view_item
row = grandparent_view_item.children.index(parent_view_item)
assert row != -1
return self.createIndex(row, 0, parent_view_item)
def view_item_from_index(self, index):
return (index.internalPointer()
if index.isValid() else self.aux_root_view_item)
if __name__ == "__main__":
run_time_item = RuntimeItem("Test", "test_12", "Test Item")
view_factory = DummieViewItemFactory(run_time_item, max_items=5000)
active_colums = ActiveColumns(["name", "id", "item_type"])
app = QtGui.QApplication(sys.argv)
tree_view = TreeView(view_factory.aux_root_view_item, active_colums)
app.setApplicationName("IPDM")
tree_view.show()
app.exec_()
More information about the PyQt
mailing list