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