Star Delegate Example¶
Demonstrates Qt’s itemview architecture
This example demonstrates the Qt model view architecture.
from PySide6.QtWidgets import QStyledItemDelegate, QStyle
from starrating import StarRating
from stareditor import StarEditor
class StarDelegate(QStyledItemDelegate):
""" A subclass of QStyledItemDelegate that allows us to render our
pretty star ratings.
"""
def __init__(self, parent=None):
super().__init__(parent)
def paint(self, painter, option, index):
""" Paint the items in the table.
If the item referred to by <index> is a StarRating, we handle the
painting ourselves. For the other items, we let the base class
handle the painting as usual.
In a polished application, we'd use a better check than the
column number to find out if we needed to paint the stars, but
it works for the purposes of this example.
"""
if index.column() == 3:
star_rating = StarRating(index.data())
# If the row is currently selected, we need to make sure we
# paint the background accordingly.
if option.state & QStyle.State_Selected:
# The original C++ example used option.palette.foreground() to
# get the brush for painting, but there are a couple of
# problems with that:
# - foreground() is obsolete now, use windowText() instead
# - more importantly, windowText() just returns a brush
# containing a flat color, where sometimes the style
# would have a nice subtle gradient or something.
# Here we just use the brush of the painter object that's
# passed in to us, which keeps the row highlighting nice
# and consistent.
painter.fillRect(option.rect, painter.brush())
# Now that we've painted the background, call starRating.paint()
# to paint the stars.
star_rating.paint(painter, option.rect, option.palette)
else:
QStyledItemDelegate.paint(self, painter, option, index)
def sizeHint(self, option, index):
""" Returns the size needed to display the item in a QSize object. """
if index.column() == 3:
star_rating = StarRating(index.data())
return star_rating.sizeHint()
else:
return QStyledItemDelegate.sizeHint(self, option, index)
# The next 4 methods handle the custom editing that we need to do.
# If this were just a display delegate, paint() and sizeHint() would
# be all we needed.
def createEditor(self, parent, option, index):
""" Creates and returns the custom StarEditor object we'll use to edit
the StarRating.
"""
if index.column() == 3:
editor = StarEditor(parent)
editor.editing_finished.connect(self.commit_and_close_editor)
return editor
else:
return QStyledItemDelegate.createEditor(self, parent, option, index)
def setEditorData(self, editor, index):
""" Sets the data to be displayed and edited by our custom editor. """
if index.column() == 3:
editor.star_rating = StarRating(index.data())
else:
QStyledItemDelegate.setEditorData(self, editor, index)
def setModelData(self, editor, model, index):
""" Get the data from our custom editor and stuffs it into the model.
"""
if index.column() == 3:
model.setData(index, editor.star_rating.star_count)
else:
QStyledItemDelegate.setModelData(self, editor, model, index)
def commit_and_close_editor(self):
""" Erm... commits the data and closes the editor. :) """
editor = self.sender()
# The commitData signal must be emitted when we've finished editing
# and need to write our changed back to the model.
self.commitData.emit(editor)
self.closeEditor.emit(editor, QStyledItemDelegate.NoHint)
if __name__ == "__main__":
""" Run the application. """
from PySide6.QtWidgets import (QApplication, QTableWidget, QTableWidgetItem,
QAbstractItemView)
import sys
app = QApplication(sys.argv)
# Create and populate the tableWidget
table_widget = QTableWidget(4, 4)
table_widget.setItemDelegate(StarDelegate())
table_widget.setEditTriggers(QAbstractItemView.DoubleClicked |
QAbstractItemView.SelectedClicked)
table_widget.setSelectionBehavior(QAbstractItemView.SelectRows)
table_widget.setHorizontalHeaderLabels(["Title", "Genre", "Artist", "Rating"])
data = [ ["Mass in B-Minor", "Baroque", "J.S. Bach", 5],
["Three More Foxes", "Jazz", "Maynard Ferguson", 4],
["Sex Bomb", "Pop", "Tom Jones", 3],
["Barbie Girl", "Pop", "Aqua", 5] ]
for r in range(len(data)):
table_widget.setItem(r, 0, QTableWidgetItem(data[r][0]))
table_widget.setItem(r, 1, QTableWidgetItem(data[r][1]))
table_widget.setItem(r, 2, QTableWidgetItem(data[r][2]))
item = QTableWidgetItem()
item.setData(0, StarRating(data[r][3]).star_count)
table_widget.setItem(r, 3, item)
table_widget.resizeColumnsToContents()
table_widget.resize(500, 300)
table_widget.show()
sys.exit(app.exec())
from PySide6.QtWidgets import (QWidget)
from PySide6.QtGui import (QPainter)
from PySide6.QtCore import Signal
from starrating import StarRating
class StarEditor(QWidget):
""" The custom editor for editing StarRatings. """
# A signal to tell the delegate when we've finished editing.
editing_finished = Signal()
def __init__(self, parent=None):
""" Initialize the editor object, making sure we can watch mouse
events.
"""
super().__init__(parent)
self.setMouseTracking(True)
self.setAutoFillBackground(True)
self.star_rating = StarRating()
def sizeHint(self):
""" Tell the caller how big we are. """
return self.star_rating.sizeHint()
def paintEvent(self, event):
""" Paint the editor, offloading the work to the StarRating class. """
painter = QPainter(self)
self.star_rating.paint(painter, self.rect(), self.palette(), isEditable=True)
# QPainter needs an explicit end() in PyPy. This will become a context manager in 6.3.
painter.end()
def mouseMoveEvent(self, event):
""" As the mouse moves inside the editor, track the position and
update the editor to display as many stars as necessary.
"""
star = self.star_at_position(event.x())
if (star != self.star_rating.star_count) and (star != -1):
self.star_rating.star_count = star
self.update()
def mouseReleaseEvent(self, event):
""" Once the user has clicked his/her chosen star rating, tell the
delegate we're done editing.
"""
self.editing_finished.emit()
def star_at_position(self, x):
""" Calculate which star the user's mouse cursor is currently
hovering over.
"""
star = (x / (self.star_rating.sizeHint().width() /
self.star_rating.MAX_STAR_COUNT)) + 1
if (star <= 0) or (star > self.star_rating.MAX_STAR_COUNT):
return -1
return star
from math import (cos, sin, pi)
from PySide6.QtGui import (QPainter, QPolygonF)
from PySide6.QtCore import (QPointF, QSize, Qt)
PAINTING_SCALE_FACTOR = 20
class StarRating(object):
""" Handle the actual painting of the stars themselves. """
def __init__(self, starCount=1, maxStarCount=5):
self.star_count = starCount
self.MAX_STAR_COUNT = maxStarCount
# Create the star shape we'll be drawing.
self._star_polygon = QPolygonF()
self._star_polygon.append(QPointF(1.0, 0.5))
for i in range(1, 5):
self._star_polygon.append(QPointF(0.5 + 0.5 * cos(0.8 * i * pi),
0.5 + 0.5 * sin(0.8 * i * pi)))
# Create the diamond shape we'll show in the editor
self._diamond_polygon = QPolygonF()
diamond_points = [QPointF(0.4, 0.5), QPointF(0.5, 0.4),
QPointF(0.6, 0.5), QPointF(0.5, 0.6),
QPointF(0.4, 0.5)]
self._diamond_polygon.append(diamond_points)
def sizeHint(self):
""" Tell the caller how big we are. """
return PAINTING_SCALE_FACTOR * QSize(self.MAX_STAR_COUNT, 1)
def paint(self, painter, rect, palette, isEditable=False):
""" Paint the stars (and/or diamonds if we're in editing mode). """
painter.save()
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setPen(Qt.NoPen)
if isEditable:
painter.setBrush(palette.highlight())
else:
painter.setBrush(palette.windowText())
y_offset = (rect.height() - PAINTING_SCALE_FACTOR) / 2
painter.translate(rect.x(), rect.y() + y_offset)
painter.scale(PAINTING_SCALE_FACTOR, PAINTING_SCALE_FACTOR)
for i in range(self.MAX_STAR_COUNT):
if i < self.star_count:
painter.drawPolygon(self._star_polygon, Qt.WindingFill)
elif isEditable:
painter.drawPolygon(self._diamond_polygon, Qt.WindingFill)
painter.translate(1.0, 0.0)
painter.restore()
© 2022 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.