25. Model-View Programming - Delegates

In Qt’s model-view architecture, a delegate sits between the view and the model and is responsible for:

  • Rendering each item in the view,
  • Providing an editor widget when the user wants to edit that item.

By default, Qt uses QStyledItemDelegate, which handles common data types automatically. You can subclass to take full control over painting and editing behavior. Both the default QStyledItemDelegate and its custom subclasses rely on QItemEditorFactory to create editor widgets. The factory maps data types to the appropriate editor. You can register editors globally or on per-delegate bases, or you can bypass the factory entirely by overriding createEditor() in the delegate subclass.

You have several options for changing how model data is displayed and edited. The first three options work directly with the default QStyledItemDelegate:

  • Use item data roles with the default QStyledItemDelegate to adjust an item’s appearance. For example, change its background color with BackgroundRole, or add an image with DecorationRole.

  • Register a standard widget for a data type with QItemEditorFactory. For example, replace the default spin box for integers with a line edit.

  • Register a custom widget for a data type with QItemEditorFactory. For example, use a custom widget made of a pair of radio buttons to edit boolean values.

  • Subclass QStyledItemDelegate to control display.

  • Subclass QStyledItemDelegate to control editing.

  • Subclass QStyledItemDelegate to handle both display and editing.

Approach Subclassing Required Custom Display Custom Editing When to Use
Item Data Roles No Limited No Simple appearance tweaks (color, icon, alignment, font)
Register Standard Widget No No Yes Replace default editor for built-in types
Register Custom Widget No No Yes Need a small custom widget (e.g., Yes/No radio buttons)
Subclass for Display only Yes Full No Complex painting (LEDs, progress bars, custom shapes)
Subclass for Editing only Yes No Full Complex editor logic or validation
Subclass for Both Yes Full Full Complete control over look and behavior

25.1 Using Item Data Roles to Customize Display

Each item in a model can return different data depending on which role the view is asking about. The table below lists the available roles and the data types they accept:

Role Accepted Types
BackgroundRole QBrush
CheckStateRole CheckState
DecorationRole QIcon, QPixmap, QImage, QColor
DisplayRole QString and types with a string representation
FontRole QFont
SizeHintRole QSize
TextAlignmentRole Alignment
ForegroundRole QBrush

By returning the appropriate value for each role in your model’s data() method, you can control how items look.

An icon of a clipboard-list1

You are developing an application that displays a table of economic indicators. Use item data roles in your model to customize the display: apply green background to positive values and red to negative, right-align and italicize negative entries, render the Aggregate column boolean values as YES/NO text, and show the ‘Include in report’ column as a checkbox.

To use item data roles to customize how the model data is displayed:

  1. Create the model. Subclass QAbstractTableModel and name it CsvModel. Initialize the table data directly in __init__() for simplicity. Also declare the column headers and two QBrush fields (one green and one red) to be used for background colors.

  2. Customize data(). Handle each role as follows:

    • DisplayRole - for the boolean columns (2 and 3), return ‘YES’ for True and ‘NO’ for `False’ values.
    • CheckStateRole - for column 3 only, return Checked for True and Unchecked for False values.
    • BackgroundRole - return the green brush if the value is True or greater than zero; return the red brush otherwise.
    • FontRole - return an italic font for False values and for numeric values less than zero.
    • TextAlignmentRole - return AlignCenter for columns 1, 2, and 3.
  3. Customize setData(). The model needs to handle two kinds of edits:

    • EditRole - for regular editable columns (1 and 2), store the new value directly and emit dataChanged to notify the view.
    • CheckStateRole - for column 3, the view passes a CheckState enum value. convert it to a plain bool before storing, then emit dataChanged.
  4. Customize flags(). For column 3 to display a checkbox, its items must be user-checkable. Return the standard flags plus ItemIsUserCheckable for that column.

Now, when you run the application you should see a window like this:

25.2 Registering Standard Widgets with QItemEditorFactory

When a user clicks to edit an item, the delegate is responsible for showing an editor widget. The delegate does not create those editors itself - it uses a QItemEditorFactory to create editors for it.

A factory maps data types to editor widgets. For each type, it holds an item editor creator, a small helper that produces the right editor widget for that type.

Qt provides a default factory that all delegates share. The table below shows the default editor widget for each supported type:

Type Editor Widget
bool QComboBox
double QDoubleSpinBox
int QSpinBox
unsigned int QSpinBox
QDate QDateEdit
QDateTime QDateTimeEdit
QPixmap QLabel
QString QLineEdit
QTime QTimeEdit
An icon of a clipboard-list1

You are building a banking application that displays a table of economic indicators. The Change (%) contains integer percentages that users need to edit. Use QItemEditorFactory to register a validated QLineEdit as the editor for integer values in that column.

To replace the default editor widget for a data type with another standard widget:

  1. Create an editor creator. Subclass QItemEditorCreatorBase and name it NumericLineEditCreator. Reimplement its createWidget()method to:
    • Create a QLineEdit object.
    • Call setAutoFillBackground(True) so the editor fully covers the table cell.
    • Create a QIntValideator and set it as the line edit’s validator. Note that the editor is constructed with the parent argument and that the creator does not keep a reference to the editor - deleting the editor is parents responsibility.

In the main window:

  1. Create a factory and register the custom creator. Instantiate a QItemEditorFactory and use registerEditor() to associate a NumericLineEditCreator with the int type. All other types keep their default editors.

  2. Create a delegate and assign factory to it. Instantiate a QStyledItemDelegate and use setItemEditorFactory() to give it the factory you just created.

  3. Assign the delegate to the target column. Call view.setItemDelegateForColumn(1, delegate) to apply the custom editor only to the ‘Change’ column.

    This is the most targeted option when using a custom view editor:

    • setItemDelegateForColumn() affects one column of one view.
    • setItemDelegate() affects the entire view
    • QItemEditorFactory.setsDefaultFactory() affects all delegates in the entire application.

The model class is the same CsvModel from the previous section, without any custom formatting:

25.3 Registering Custom Widgets with QItemEditorFactory

You are not limited to Qt’s built-in widgets when providing editors - you can use your own custom widgets just as easily.

An icon of a clipboard-list1

You are extending the banking application’s indicator table. The ‘Aggregate’ and Include in report’ columns hold boolean values that users need to edit. Use QItemEditorFactory to register a custom ‘Yes/No’ radio button widget for boolean values in those columns.

To register a custom widget as an editor:

  1. Create the custom editor widget. Subclass QWidget and lay out two radio buttons horizontally, one for True (‘Yes’) and one for False (‘No’). Add a value property, with a getter and setter, and mark it as the user property with user=True. This is the property that the delegate uses as the bridge to the model: it populates the editor by writing to value when the editor opens, and reads value back to store the result when the editor closes. If you want to bind a different property to the model, reimplement QItemEditorCreatorBase.valuePropertyName() in your creator class.
  1. Create an editor creator. Subclass QItemEditorCreatorBase and reimplement createWidget(). Since all the widget logic lives in the widget class itself, the creator is minimal - it just returns a new instance of your widget.
  1. Put everything together,following the same steps as in the previous section:
    • Create a QItemEditorFactory and call registerEditor() to associate your creator with the bool type.
    • Instantiate a QStyledItemDelegate and assign the factory to it with setItemEditorFactory().
    • Call view.setItemDelegateForColumn() for each target column.

The model class remains the same as in the previous section.

25.4 Custom Display in QStyledItemDelegate

The QStyledItemDelegate.paint() method is called by the view whenever an item needs to be drawn. Its signature is:

1 `QStyledItemDelegate.paint(painter, option, index)`

where:

  • painter is a QPainter, that performs the actual drawing.
  • option is a QStyleOptionViewItem, a snapshot of the drawing parameters for this item, including the available drawing area, palette and font.
  • index is a `QModelIndex, which is used to locate the item’s data in the model.

You can subclass QStyledItemDelegate and reimplement paint() to take full control over how an item looks in the view.

An icon of a clipboard-list1

You are extending the banking application’s indicator table. Subclass QStyledItemDelegate and reimplement paint() to render boolean values in the ‘Aggregate’ and ‘Include in report’ columns as LED indicators instead of text.

To customize how items are displayed:

  1. Subclass QStyledItemDelegate. Create a subclass named LedDelegate and reimplement paint(). In paint():

    • Retrieve the item’s value from the model via index.data().
    • Call draw_led() to paint the LED in the item’s cell.
    • Call super.paint() to let the base class draw selection highlighting and fill the cell background when its editor is opened.
  2. Paint the LED. Extract the drawing logic into a helper method named draw_led(). In this method:

    • Use option.rect to calculate the drawing area. Cap the diameter and add padding.
    • Define two colors, green for True values and red for False.
    • Create a QGradialRadient to give the LED somewhat realistic appearance.
    • Call drawEllipse() twice, once for the dark outer ring, and once for the gradient-filled dome on top.
  1. Create the delegate. In the main window instantiate LedDelegate().

  2. Assign the delegate to the target columns. Call setItemDelegateForColumn() for columns 2 and 3, which hold the boolean values.

When you run the application, both boolean columns display green LEDs for True values and red LEDs for False. Double-clicking a cell still opens the default combobox editor for booleans- custom painting does not affect editing behavior.

25.5 Custom Editors in QStyledItemDelegate

In the previous section we customized how items are displayed by reimplementing paint(). To also provide a custom editor, you need to reimplement three additional methods:

  • createEditor(parent, option, index), which creates and returns the editor widget for the item.
  • setEditorData(editor, index), which populates the editor with the item’s current value from the model.
  • setModelData(editor, model, index), which reads the value from the editor and stores it in the model.
An icon of a clipboard-list1

You are extending the banking application’s indicator table. Subclass QStyledItemDelegate to provide a custom LED button widget for editing boolean values.

To provide a custom editor:

  1. Extract the LED drawing logic into a standalone function. Both the delegate’s paint() method and the editor widget’s button need to draw LEDs.
  1. Create the custom editor widget. The editor widget consists of two buttons laid out horizontally, each displaying a LED - green for True on the left, red for False on the right. First create a QPushButton subclass that reimplements paintEvent() to draw a LED using draw_led() instead of showing text. Also create a QWidget subclass that holds two LedButtons in a horizontal layout. Add a value property with a getter and setter, and reimplement keyPressEvent() so the user can toggle the value by pressing the left or right arrow keys.
  1. Subclass QStyledItemDelegate. Create LedDelegate and reimplement the four methods:

    • paint() drows the LED in the cell, as in the previous section.
    • createEditor() simply creates and returns a LedWidget.
    • setEditorData() writes the item’s current value to LedWidget.value.
    • setModelData() reads LedWidget.value and writes it back to the model via setData().

In the main window, create the model, view and delegate and assign the delegate to columns 2 and 3, as in the previous sections.

Now, when you run the application and double-click a boolean cell, the custom editor opens in place.

25.6 Combining Display and Editing in Delegates

The previous section showed how to provide both custom display and a custom editor widget. For boolean values, however, opening an editor on double-click is more ceremony that necessary - a single click or keystroke is enough to toggle the value. Qt supports this through editorEvent(), a method the delegate receives mouse and key events through even when no editor is opened.

An icon of a clipboard-list1

You are extending the banking application’s indicator table. Create a delegate that paints LED indicators for boolean columns and handles editing without creating any editor widget.

To combine display and editing in a delegate without an editor widget:

  1. Reimplement createEditor() to return None. This suppresses the default editor - double-clicking a cell does nothing.

  2. Reimplement paint() to draw LEDs, as in the previous sections.

  3. Reimplement editorEvent():

    • For a MouseButtonRelease event: if the left mouse button was released inside the cell (option.rect.contains(event.pos())), read the current value from the model, toggle it, and write it back.
    • For a KeyPress event: if the key is Space, read the current value from the model, toggle it, and write it back.
    • For all other events, call the base class implementation.

When you run the application, boolean cells can be toggled with a single left click or by pressing Space.

25.7 Persistent Editors in Views

In all previous sections, editors appeared only while the user was actively editing, opening on double-click or keypress and closing when done. A persistent editor stays visible in the cell at all times, with no click required to activate it. This is useful when you want certain fields to be immediatelly editable without any extra interaction.

Persistent editors are opened by calling view.openPersistentEditor() for each cell that should have one. The view than keeps the editor widget visible for the lifetime of that cell.

An icon of a clipboard-list1

You are extending the banking application’s indicator table. Make the Change (%) column always show a validated line edit so users can edit valuse directly without double-clicking.

To open persistent editors on a column:

  1. Create the NumericLineEditCreator class:
    • Reimplement createWidget() to create a QLineEdit as the editor and install a custom int validator on it.
    • Reimplement valuePropertyName() to return 'text' instead of relying on a user property. This tells the delegate to read and write the line edit’s text property when transferring data to and from the model.
  1. In the model’s setData() then convert the string back to int before storing it.
  1. Set up the factory and the delegate, as in section 25.2. Register NumericLineEditorCreator for the int type and assign the delegate to column 1.

  2. Open a persistent editor for each cell in the column. After the model and view are set up, loop over the row and call view.openPersistentEditor() for each index in column 1. The view immediately instantiates the editor widget for each cell. Users can edit values directly without double-clicking.