[PyQt] Code-Example: Performance of QTreeView

Knacktus knacktus at googlemail.com
Sun Oct 10 09:54:59 BST 2010


Hi all,

here's an example of my code. It've tried to make minimal ... In my 
E-Mail client some lines are wrapped. But I think if you copy and paste 
it to your editor you should be able to run it in one module (using 
Python 2.7 and PyQt 4.7.4). (It worked for me)
Performance is even worse then in my real world example, but here I have 
a one-child-per-parent structure. Therefore it's as deep as it can get.
So that seems to matter a lot.

Cheers,

Jan

Here's the code with comments:




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 is 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