22. Model-View Programming with QAbstractListModel

This chapter introduces custom Qt models using QAbstractListModel through a series of simple demonstrations manipulating a list of company clients.

The Qt model-view architecture is Qt’s variant of the model-view-controller (MVC) design pattern where, per the documentation, the controller and view are combined. The model and view are decoupled, allowing the same model to be used with multiple views.

Qt provides several abstract classes you can subclass for custom models, such as:

  • QAbstractItemModel
  • QAbstractTableModel
  • QAbstractListModel

Qt also offers ready-to-use concrete models, including:

  • QStandardItemModel
  • QFileSystemModel
  • QSqlQueryModel

Earlier examples demonstrated some of these with Qt view classes. Here, we focus on creating custom models.

22.1 Read-only List Model

[TODO: QAbstractListModel inheritance tree]

Of the three abstract model classes above, QAbstractListModel is the easiest to subclass. It provides default implementations for several QAbstractItemModel methods and offers a specialized interface for simple, non-hierarchical sequences of items (lists).

Like other model classes, a QAbstractListModel subclass acts as an intermediary between a data source and a view:

[TODO: DATA-MODEL-VIEW DIAGRAM]

A data source can range from a simple Python list or structured text file, relational database data, and anything in between. The model’s role is to supply data in a format that a Qt view can read and display.

To create a read-only list model you must implement at least two methods:

  • rowCount(): Returns the number of rows in the model. (e.g., len(lst) for a Python list, line count for a file, or count(*) for a SQL query). The parent parameter is for hierarchical model and unused here.
  • data(index, role): Returns the data for the given role and item referenced by the index. For list models, index is a QModelIndex object with column() as zero and row() indicating the position in the underlying data. The role is one of the Qt.ItemDataRole enumeration values(default: DisplayRole).
An icon of a clipboard-list1

You have a text file listing company clients (‘data.txt’) and you need to display them in a list view.

To achieve this:

  1. Subclass QAbstractListModel and provide access to the data source. Here, read the text file entirely in __init__() and store lines in a Python list (txt_data), where each element is a single string (a client’s name and profession). For other types of data sources you can retrieve data dynamically instead.

  2. Implement rowCount(). View classes call this method to determine the model’s length. Here, return len(self.txt_data). Qt list models default to one column.

  3. Implement data(). This returns model data for a given index and role. Use index.row() to access txt_data. Return data only for DisplayRole (the text for display); return None otherwise.

  4. Optionally, implement headerData() for row or column headers. Here, the single column header is ‘Clients’. QListView does not display headers but QTableView would.

QModelIndex:

This class helps views locate model items. Qt models are table-based - think of the model in this example as a one-column table with n rows. Use row() and column() to reference cells.

QAbstractItemModelTester

This class aids model development. Pass your model instance to its constructor - it logs implementation errors to the console, helping catch issues early.

This demo is read-only; next sections cover editable and resizable models.

22.2 Editable List Model

As shown in the read-only list model example, creating a basic list model requires implementing at least rowCount() and data(). To make it editable, add two more methods:

  • setData(index, value, role): Sets the data for the given role (typically EditRole) and index, and returns True if successful; otherwise returns False. If set successfully, emit the dataChanged() signal.
  • flags(index): Returns the item flags for the index. The base implementation enables and selects items. To allow editing, add Qt.ItemFlags.ItemIsEditable.
An icon of a clipboard-list1

You have implemented a read-only model of a company clients backed by a text file (‘data.txt’) and you need to make it editable.

To create an editable list model:

  1. Create a QAbstractListModel subclass and the access to the data source.

  2. Implement the rowCount() and data() methods just as you did for the read-only model.

  3. Implement the setData() method. setData() accepts three arguments: index, value and role. In the method we check if the role is equal to Qt.ItemDataRole.EditRole and, if it is, we set the model data for the index.row() to value using self.txt_data[index.row()] = value. If the data is set successfully we emit the dataChanged signal and the method returns True. Otherwise it returns False.

  4. Implement the flags() method. In this method we signal to views that the model data is editable by adding Qt.ItemFlags.ItemIsEditable to the flags. Note that we can make the model items editable selectively by using the index parameter. In the example we ignore index which means that all the model items are editable.

Now if you double-click any of the list view lines you are able to edit it and the changes are saved in the model (ie. to the TxtFileModel.txt_data list and signaled by the dataChanged signal. You can also implement the logic to update the text file from the txt_data values which we omit in this example.

22.3 Editable List Model with Data-Widget Mapping

The QDataWidgetMapper class lets you map a data model row (or column) to a set of widgets, making them data-aware. When the model’s current index changes, mapped widgets are updated with data from the model. This is useful for creating forms enhancing user experience in viewing and editing data.

An icon of a clipboard-list1

You have an editable model of a list of company clients. Editing is enabled by double-clicking a client row in the list view. Your task is to make editing more user-friendly.

  1. Create a QAbstractListModel subclass to represent your model. Then, in the main window class:
  1. Create your model object and the widgets for displaying and editing your model data.

  2. Create the mapper object and use QDataWidgetMapper.setModel() to connect your model to it.

  3. Use QDataWidgetMapper.addMapping() to map your model columns with the widgets. The example model has only one column, which we map to a QLineEdit widget.

  4. Synchronize the view’s current item with the model’s current index so both update when the user changes the view’s selection.

In the example, we use manual submit policy, updating the model via a ’Submit` button. This syncs the line edit with the current view item, allowing easy updates by editing the line edit value and clicking ‘Submit’.

22.4 Resizable List Model

For a basic QAbstractListModel subclass, you need to implement at least two methods: rowCount() and data(). To make the model editable, you need to implement two more: setData() and flags(). To be able to add or remove rows, you need to implement insertRows() and removeRows().

An icon of a clipboard-list1

You have an editable model of a list of company clients. You need to enable the user to insert or remove clients from it.

To make a resizable QAbstractListModel subclass:

  1. Create a subclass of the QAbstractListModel class. As in previous examples, read the data from a text file and store it in a Python list named self.txt_data.

  2. Implement the insertRows() method. For simplicity, the example inserts rows filled with template text (“<insert row data>”). Guard the data insertion with beginInsertRows() (to signal connected views that rows are about to be inserted) and endInsertRows(). This pair of methods ensures that views remain in a valid state. beginInsertRows() takes three arguments: parent (an invalid QModelIndex() in our case), first (the starting row number post-insertion) and last (the ending row number post-insertion). The method returns True on success or False otherwise.

  3. Implement the removeRows() method. This removes rows from the self.txt_data, enclosed by beginRemoveRows() and endRemoveRows(), and returns True on success or False otherwise.

Then, in your main class

  1. Create three QPushButtons: insert_button, append_button and remove_button. Handle the insert button’s clicked() signal with a slot named on_insert() calling insertRow() to insert a single row by invoking insertRows(). Similarly, handle the append button’s clicked() signal with on_append() to insert a row at the end.