Is there a way to add sections to QListView in PySide or PyQt?

This question is an exact duplicate of this unanswered question, except that I'm using Python.

I've got this.

And am looking for this.

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

  • 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.
  • 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.
  • Subclass QListView, but I'm struggling to understand what to override.
  • 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

  • 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:

  • How do I assign them their corresponding data?
  • How do I sort them into place, above their "child" items?
  • 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_()
    

    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.


    Ok, you intend to connect the other views (such as the Pie chart) to a QAbstractListModel descendant. I guess this is possible but it is not that common. Hence the confusion. IHMO the Qt Model classes are not that great and I wouldn't use them if I didn't have a table or tree view. Nothing stops you from making your own model class and populating a QListWidget from it.

    But let's say you still want to put your data in a QAbstractListModel . I agree that adding the sections as virtual items is a bad idea, so your option 1 is out.

    Using a proxy model is a good option I think. You can connect all your QListViews to a proxy and all other views (eg the Pie chart) to the underlying source model. The proxy model does then include the sections as items. I think you need only one proxy since the sections will be the same for all list views.

    Instead of subclassing from a QListView you could use a delegate to customize how the cells are rendered. Take a look at the star delegate example from the Qt documentation.

    Option 4, writing your own view, should really be your last resort. You basically are rolling out your own version QListView. It is a lot of work and will never be as good as the Qt original.

    Hope this helps.

    链接地址: http://www.djcxy.com/p/79004.html

    上一篇: 为什么Rails 4.0 RC1中没有assets / images目录

    下一篇: 有没有办法在PySide或PyQt中添加章节到QListView?