Is there a way to add sections to QListView in PySide or PyQt?
Question: This question is an exact duplicate of
[this](http://stackoverflow.com/questions/25560547/is-there-a-way-to-add-
sections-to-qlistview) unanswered question, except that I'm using Python.

I've got this.

[![enter image description
here](http://i.stack.imgur.com/5qOhq.png)](http://i.stack.imgur.com/5qOhq.png)

And am looking for this.

[![enter image description
here](http://i.stack.imgur.com/sCX8o.png)](http://i.stack.imgur.com/sCX8o.png)

I'm looking for hints as to how to approach this. Here's what I've considered
so far.

  1. Add "virtual items" to the model itself. I'd rather not do this, in order to keep the model free of view related data. I intend to add additional views onto this model.
  2. Add a proxy model per view. The proxy could add additional items and sort them appropriately. Albeit cleaner than (1), I'm not entirely convinced for the same reasons.
  3. Subclass QListView, but I'm struggling to understand what to override.
  4. Just write my own view; with a for-loop and QLabels, and synchronise with the model best I can. Gotta do what you gotta do.

Help!

Source.

    
    
    import sys
    from PySide import QtCore, QtGui
    
    
    Label = QtCore.Qt.DisplayRole
    Section = QtCore.Qt.UserRole + 1
    
    
    class Model(QtCore.QAbstractListModel):
        def __init__(self, parent=None):
            super(Model, self).__init__(parent)
            self.items = list()
    
        def data(self, index, role):
            item = self.items[index.row()]
            if role == Label:
                return item["label"]
    
            if role == Section:
                return item["section"]
    
        def append(self, item):
            """Append item to end of model"""
            self.beginInsertRows(QtCore.QModelIndex(),
                                 self.rowCount(),
                                 self.rowCount())
    
            self.items.append(item)
            self.endInsertRows()
    
        def rowCount(self, parent=None):
            return len(self.items)
    
    app = QtGui.QApplication(sys.argv)
    model = Model()
    
    for item in ({"label": "Ben", "section": "Human"},
                 {"label": "Steve", "section": "Human"},
                 {"label": "Alpha12", "section": "Robot"},
                 {"label": "Mike", "section": "Toaster"}):
        model.append(item)
    
    view = QtGui.QListView()
    view.setWindowTitle("My View")
    view.setModel(model)
    view.show()
    
    app.exec_()
    

  

### Update 1 - Additional Information

For clarity, this question is about QListView rather than about alternatives
to it. The reason being that the rest of the application is developing in an
MVC-like fashion, where one or more views are drawing the unique set of data
present in the one model.

This particular view includes sections, the other views, which aren't
necessarily QListView's, shouldn't know anything about sections. For example,
one view may be a counter, listing the number of items available. Another
might be a pie, showing the ratio between items starting with the letter 'A'.

For further reference, what I'm looking for is exactly what ListView does in
QML.

  * <http://doc.qt.io/qt-5/qml-qtquick-listview.html#section-prop>

That is, a single model with an additional delegate for optional sections. In
this case, the view doesn't require the model to contain these added members,
but rather draws them based on the existing data.

### Example

  * [Source](https://github.com/pyblish/pyblish-qml/blob/3c420ac35ce286420475eeff73181f1b5738af26/pyblish_qml/qml/Overview.qml#L85)

![](https://cloud.githubusercontent.com/assets/2152766/7021167/f893ebd0-dd16-11e4-9ae2-bc23b4767f98.gif)

  

### Update 2 - Work in progress

Ok, so I've got the extra items added to the bottom of the view using a
QSortFilterProxyModel, but I'm struggling to understand:

  1. How do I assign them their corresponding data?
  2. How do I sort them into place, above their "child" items?

[![enter image description
here](http://i.stack.imgur.com/y7inR.png)](http://i.stack.imgur.com/y7inR.png)

    
    
    import sys
    from PySide import QtCore, QtGui
    
    
    Label = QtCore.Qt.DisplayRole
    Section = QtCore.Qt.UserRole + 1
    IsSection = QtCore.Qt.UserRole + 2
    
    
    class Item(object):
        @classmethod
        def paint(cls, painter, option, index):
            rect = QtCore.QRectF(option.rect)
    
            painter.save()
    
            if option.state & QtGui.QStyle.State_MouseOver:
                painter.fillRect(rect, QtGui.QColor("#DEE"))
    
            if option.state & QtGui.QStyle.State_Selected:
                painter.fillRect(rect, QtGui.QColor("#CDD"))
    
            painter.drawText(rect.adjusted(20, 0, 0, 0),
                             index.data(Label))
    
            painter.restore()
    
        @classmethod
        def sizeHint(cls, option, index):
            return QtCore.QSize(option.rect.width(), 20)
    
    
    class Section(object):
        @classmethod
        def paint(self, painter, option, index):
            painter.save()
            painter.setPen(QtGui.QPen(QtGui.QColor("#666")))
            painter.drawText(QtCore.QRectF(option.rect), index.data(Label))
            painter.restore()
    
        @classmethod
        def sizeHint(self, option, index):
            return QtCore.QSize(option.rect.width(), 20)
    
    
    class Delegate(QtGui.QStyledItemDelegate):
        def paint(self, painter, option, index):
            if index.data(IsSection):
                return Section.paint(painter, option, index)
            else:
                return Item.paint(painter, option, index)
    
        def sizeHint(self, option, index):
            if index.data(IsSection):
                return Section.sizeHint(option, index)
            else:
                return Item.sizeHint(option, index)
    
    
    class Model(QtCore.QAbstractListModel):
        def __init__(self, parent=None):
            super(Model, self).__init__(parent)
            self.items = list()
    
        def data(self, index, role):
            item = self.items[index.row()]
    
            return {
                Label: item["label"],
                Section: item["section"],
                IsSection: False
            }.get(role)
    
        def append(self, item):
            self.beginInsertRows(QtCore.QModelIndex(),
                                 self.rowCount(),
                                 self.rowCount())
    
            self.items.append(item)
            self.endInsertRows()
    
        def rowCount(self, parent=None):
            return len(self.items)
    
    
    class Proxy(QtGui.QSortFilterProxyModel):
        def data(self, index, role):
            if index.row() >= self.sourceModel().rowCount():
    
                return {
                    Label: "Virtual Label",
                    Section: "Virtual Section",
                    IsSection: True
                }.get(role)
    
            return self.sourceModel().data(index, role)
    
        def rowCount(self, parent):
            sections = 0
    
            prev = None
            for item in self.sourceModel().items:
                cur = item["section"]
    
                if cur != prev:
                    sections += 1
    
                prev = cur
    
            # Note: This includes 1 additional, duplicate, section
            # for the bottom item. Ordering of items in model is important.
            return self.sourceModel().rowCount() + sections
    
        def index(self, row, column, parent):
            return self.createIndex(row, column, parent)
    
        def mapToSource(self, index):
            if not index.isValid():
                return QtCore.QModelIndex()
    
            return self.sourceModel().createIndex(index.row(),
                                                  index.column(),
                                                  QtCore.QModelIndex())
    
        def parent(self, index):
            return QtCore.QModelIndex()
    
    
    app = QtGui.QApplication(sys.argv)
    model = Model()
    
    for item in ({"label": "Ben", "section": "Human"},
                 {"label": "Steve", "section": "Human"},
                 {"label": "Alpha12", "section": "Robot"},
                 {"label": "Mike", "section": "Toaster"},
                 {"label": "Steve", "section": "Human"},
                 ):
        model.append(item)
    
    proxy = Proxy()
    proxy.setSourceModel(model)
    
    delegate = Delegate()
    
    view = QtGui.QListView()
    view.setWindowTitle("My View")
    view.setModel(proxy)
    view.setItemDelegate(delegate)
    view.show()
    
    app.exec_()
    


Answer: What you want is a `QTreeWidget` (or `QTreeView` if you want separate
model/views, but you'll have to create your own model for that to work).

    
    
    tree = QtGui.QTreeWidget()
    tree.setHeaderLabels(['Name'])
    
    data = ({"label": "Ben", "section": "Human"},
            {"label": "Steve", "section": "Human"},
            {"label": "Alpha12", "section": "Robot"},
            {"label": "Mike", "section": "Toaster"})
    
    sections = {}
    for d in data:
        sections.setdefault(d['section'], []).append(d['label'])
    
    for section, labels in sections.items():
        section_item = QtGui.QTreeWidgetItem(tree, [section])
        for label in labels:
            QtGui.QTreeWidgetItem(section_item, [label])
    

The only other option would be to use a `QListWidget/QListView` and use a
`QItemDelegate` to draw your _section_ items differently than your _label_
items.

