Qt modelview: delegates
Delegates allow you to add customized representations of model-data within your view. Some reasons this may be useful:
- you want to customize the item's editor (ex: add qvalidator, launch file-browser, ...)
- you want to customize how the item is painted (ex: five star-rating, locked/unlocked)
- you want to show data differently than it is stored in model (ex: dates in local timezone, de-serialized json, ...)
WARNING:
Apparently, I have two pages dedicated to this. See Python qt: delegates
Painting
Repainting Cell
You can emit
model.dataChanged(index, index)
to force a repaint of that cell in all views.
Painting Widgets
If you are imitating a real Qt widget, there are tools to assist with painting them.
QStyle.drawControl()
QStyle.drawPixmap()
draw custom pixmap (seeQPixmap.grabWidget()
(Qt4) andQWidget.grab()
(Qt5))QStyle.drawItemText()
writes text- ... etc ...
TODO:
Document use of
styleoption
for creating entirely default painted objects.def paint(self, painter, option, index): qapp = QtWidgets.QApplication.instance() qstyle = qapp.style() button = QtWidgets.QStyleOptionButton() button.rect = self._get_button_qrect(option) button.styleObject = qstyle button.palette = option.palette button.fontMetrics = option.fontMetrics button.text = index.data() # manipulate button-state button.state = option.state | qstyle.State_Sunken | qstyle.EnabledNormal
class Delegate(QtWidgets.QStyledItemDelegate): def paint(self, painter, option, index): """ Paints the model-data text. """ qapp = QtWidgets.QApplication.instance() qstyle = qapp.style() # get rect of text (within margins) text_rect = qstyle.subElementRect(qstyle.SE_ItemViewItemText, option) # paint text, observing view's text-alignment painter.drawText(text_rect, option.displayAlignment, index.data())QProgressbar
class Delegate(QtWidgets.QStyledItemDelegate): def paint(self, painter, option, index): current_progress = float(index.data()) styleopts = QtWidgets.QStyleOptionProgressBar() styleopts.rect = option.rect styleopts.minimum = 0.0 styleopts.maximum = 100.0 styleopts.progress = current_progress QtWidgets.QApplication.style().drawControl( QtWidgets.QStyle.CE_ProgressBar, styleopts, painter, )See Also
- See https://github.com/pyside/Examples/blob/master/examples/itemviews/stardelegate/stardelegate.py
- :py:obj:`QtWidgets.QStyleOptionProgressBar`
QPushButton
class Delegate(QtWidgets.QStyledItemDelegate): def paint(self, painter, option, index): qapp = QtWidgets.QApplication.instance() qstyle = qapp.style() # core styleopts = QtWidgets.QStyleOptionButton() styleopts.rect = option.rect styleopts.text = index.data(QtCore.Qt.DisplayRole) styleopts.icon = index.data(QtCore.Qt.DecorationRole) # style/state styleopts.state = option.state # State_Enabled, State_Sunken, State_Raised, State_MouseOver, ... styleopts.styleObject = qstyle styleopts.fontMetrics = option.fontMetrics styleopts.palette = option.palette qstyle.drawControl(QtWidgets.QStyle.CE_PushButton, styleopts, painter)QComboBox
Copy Widget Pixmap (best -- closest to real qss stylesheet)class Delegate(QtWidgets.QStyledItemDelegate): def paint(self, painter, option, index): """ paints combobox using a real QComboBox, styled exactly exactly like a normal qcombobox. """ # build widget text = index.data(QtCore.Qt.DisplayRole) combo = QtWidgets.QComboBox() combo.addItems([text]) combo.resize(option.rect.width(), option.rect.height()) # steal pixmap of widget pixmap = widgetcompatUtils.grab(combo) # paint pixmap painter.drawPixmap( option.rect, # target-rect pixmap, # pixmap combo.rect(), # source-rect )Or you may paint using drawComplexControl
class Delegate(QtGui.QStyledItemDelegate): def paint(self, painter, option, index): styleopts = QtWidgets.QStyleOptionComboBox() styleopts.rect = option.rect styleopts.palette = option.palette style = QtWidgets.QApplication.instance().style() style.drawComplexControl( QtWidgets.QStyle.CC_ComboBox, styleopts, painter, ) style.drawItemText( painter, option.rect, option.displayAlignment, option.palette, True, index.data(QtCore.Qt.DisplayRole), )QPixmap
usingQStyle.drawItemPixmap()
(simple)class ToolLockDelegate(QtWidgets.QStyledItemDelegate): def __init__(self, *args, **kwargs): super(ToolLockDelegate, self).__init__(*args, **kwargs) self._pixmap = QtGui.QPixmap('file.png') def paint(self, painter, option, index): QtWidgets.QApplication.style().drawItemPixmap( painter, option.rect, QtCore.Qt.AlignCenter, self._pixmap, )By hand (complex).
(NOTE - pixmap is not centered, and scales larger than it should).
class LockDelegate(QtWidgets.QStyledItemDelegate): clicked = QtCore.Signal(QtCore.QModelIndex) def __init__(self, *args, **kwargs): super(LockDelegate, self).__init__(*args, **kwargs) general_icons = icons.GeneralIcons() self._pixmap = QtGui.QPixmap('some/file.png') def paint(self, painter, option, index): painter_opacity = painter.opacity() # paint the pixmap height = option.rect.height() scaled_pixmap = self._pixmap.scaledToHeight(height) width = scaled_pixmap.rect().width() painter.drawPixmap(option.rect.x(), option.rect.y(), width, height, scaled_pixmap) # restore painter opacity painter.setOpacity(painter_opacity)Painting Selection
In simple cases, this should not be required.
If you are replacing the contents of the cell with a pixmap or the like, this is how to render the current selection.class Delegate(QtWidgets.QStyledItemDelegate): def paint(self, painter, option, index): style = QtWidgets.QApplication.style() # paint selection if option.state & QtWidgets.QStyle.State_Selected: painter.fillRect(option.rect, option.palette.highlight()) # paint mouseover elif option.state & QtWidgets.QStyle.State_MouseOver: base_qcolor = option.palette.base().color() mouseover_qcolor = base_qcolor.lighter() mouseover_brush = QtGui.QBrush(mouseover_qcolor) painter.fillRect(option.rect, mouseover_brush) # ... your custom paint instructions ...
Registering Clicks
Normally, the view decides the type of event that triggers editor-creation (ex: qlineedit, calendar, etc).
You do not have to be restricted to this to register single-clicks.
QtWidgets.QItemDelegate.editorEvent()
is called on mouseup/mousedown on the delegate.Example Button within Cell
def Delegate(QtWidgets.QItemDelegate): _button_width = 80 def paint(self, painter, option, index): self._paint_text(painter, option, index) self._paint_button(painter, option, index) def editorEvent(self, event, model, option, index): """ Prints for single-clicks within button qrect. """ # ignore if not a single click if event.type() != QtCore.QEvent.MouseButtonRelease: return True # confirm click on region with button qrect = self._get_button_rect(option) if qrect.contains(event.pos()): print('single click on button region!') # ===================== # \/ private methods \/ # ===================== def _paint_button(self, painter, option, index): """ Adds button to rightmost 80px of cell. """ qapp = QtWidgets.QApplication.instance() qstyle = qapp.style() button = QtWidgets.QStyleOptionButton() button.rect = self._get_button_rect(option) button.text = 'click me!' button.state = option.state button.styleObject = qstyle button.palette = option.palette button.fontMetrics = option.fontMetrics qstyle.drawControl(qstyle.CE_PushButton, button, painter) def _paint_text(self, painter, option, index): """ Paints the model-data text. """ qapp = QtWidgets.QApplication.instance() qstyle = qapp.style() # get rect of text (within margins) text_rect = qstyle.subElementRect(qstyle.SE_ItemViewItemText, option) # paint text, observing view's text-alignment painter.drawText(text_rect, option.displayAlignment, index.data()) def _get_button_rect(self, option): """ Generates button qrect from QStyleOptionViewItem obtained by paint/editorEvent. """ x = option.rect.left() + option.rect.right() - self._button_width y = option.rect.top() qrect = QtCore.QRect(x, y, self._button_width, option.rect.height()) return qrect
Custom Editor
QStyledItemDelegate methods createEditor(self, parent, option, index)
returns editor widget setEditorData(self, editor, index)
load the editor with your data (performing any adjustments required) setModelData(self, editor, model, index)
save the editor's data into the model (performing any adjustments/validation required)
Buttons in DataCell
See https://stackoverflow.com/questions/11777637/adding-button-to-qtableview