Home | All Classes | Main Classes | Annotated | Grouped Classes | Functions

Qt 4: The Interview Classes

The Interview classes provide a model/view framework for Qt applications based on the well known Model-View-Controller design pattern. In this document, we will describe Qt's model/view architecture, discuss the current state of the implementation, provide some examples, show the improvements offered over Qt 3, and indicate the future direction of the implementation.

Overview of The Model/View Architecture

Model-View-Controller (MVC) is a design pattern originating from Smalltalk that is often used when building user interfaces. In Design Patterns, Gamma et al. write:

MVC consists of three kinds of objects. The Model is the application object, the View is its screen presentation, and the Controller defines the way the user interface reacts to user input. Before MVC, user interface designs tended to lump these objects together. MVC decouples them to increase flexibility and reuse.

If the view and the controller objects are combined, the result is the model/view architecture. This still separates the data in the model from its presentation to the user, but simplifies both the design and implementation of a framework based on these principles. In Interview, we also introduce the concept of the delegate to provide further user input functionality. The advantage of using a delegate is that it allows input methods to be defined instead of those provided by the view.

Interview defines the interfaces and common functionality for the models, views, and delegates at the abstract level. All implementations subclass the relevant abstract class: QAbstractItemModel, QAbstractItemView, or QAbstractItemDelegate. This approach ensures a common set of interfaces for interoperability between each of the components.

Interview provides ready-to-use implementations of views for the common table, tree, and list widgets: QTableView, QTreeView, and QListView. These reimplement and extend the interfaces provided by the abstract views to provide features for particular use cases.

Two specialized abstract models are provided for use with the view classes: QAbstractTableModel is a useful starting point for providing a model to use with QTableView; QAbstractListModel can be subclassed to produce a list-based model to use with QListView. The QDirModel is provided as an example model, providing directory information for use with QListView and QTreeView.

At the convenience level, we find classes that are provided to help those who are familiar with the conventional list, tree, and table widgets: QListWidget, QTreeWidget, and QTableWidget. These present a simplified interface to the developer that does not explicitly require a knowledge of the underlying model/view architecture.

The view widgets interact with selection objects to ensure that items are selected in a consistent way, and that selections can be shared between different views of the same model.

For details about how to use the model/view classes, see the Model/View Programming document.

See also the Database GUI Layer document for information about Qt 4's database models.

The Current State of Interview

The following features are provided with the technology preview.

Model classes that provide common interfaces, or that manage and store data:

View classes that provide common interfaces, or that present data from models in a standard way:

Delegate and controller classes that provide common interfaces, or that provide editing and control facilities for views:

Convenience classes that provide item-based functionality found in Qt 3's item view widgets:

Example Code

To illustrate how the Interview classes are used, we present two examples that show different aspects of the model/view architecture.

Sharing a Model Between Two Views

In this example, we display the contents of a model using two different views, and share the user's selection between them. We will use the QDirModel supplied with Qt because it requires very little configuration, and provides existing data to the views.

The main() function for this example demonstrates all the principles involved in setting up the model, the views, and the selection:

    int main(int argc, char *argv[])
    {
        QApplication app(argc, argv);
        QSplitter *splitter = new QSplitter;

        QDirModel *model = new QDirModel(QDir(), splitter);

        QTreeView *tree = new QTreeView(splitter);
        QListView *list = new QListView(splitter);

        tree->setModel(model);
        list->setModel(model);

        QItemSelectionModel *selection = new QItemSelectionModel(model, splitter);

        tree->setSelectionModel(selection);
        list->setSelectionModel(selection);

        splitter->setWindowTitle("Two views onto the same directory model");
        splitter->show();
        app.setMainWidget(splitter);

        return app.exec();
    }

In the above function, we constructed a directory model to display the contents of a default directory. The two views are constructed and given the same model to work with. By default, each view will maintain and display its own selection of items from the model, but we explicitly share a new selection with both the tree view and the list view. As a result, changes to the selection in either of these views will automatically cause the selection in the other to change.

The model/view architecture allows us to replace the QDirModel in this example with a completely different model, one that will perhaps obtain data from a remote server, or from a database.

Creating a Custom Model

In this example, we display the contents of a simple read-only model using a ready-made view class.

Creating a custom model for the existing views requires us to subclass QAbstractListModel and reimplement a standard set of functions. Our model will take a list of strings when constructed, and supply these to a view. Since we only require a simple read-only model, the number of functions we need to provide are very few.

The complete declaration of our model is as follows:

    class StringListModel : public QAbstractListModel
    {
        Q_OBJECT
    public:
        StringListModel(const QStringList &strings, QObject *parent = 0)
            : QAbstractListModel(parent), stringList(strings) {}

        int rowCount() const;
        QVariant data(const QModelIndex &index, int role) const;
        QVariant headerData(int section, Qt::Orientation orientation,
                            int role = DisplayRole) const;

        ItemFlags flags(const QModelIndex &index) const;
        bool setData(const QModelIndex &index, int role, const QVariant &value);

        bool insertRows(int position, const QModelIndex &index, int rows);
        bool removeRows(int position, const QModelIndex &index, int rows);

    private:
        QStringList stringList;
    };

Since the underlying data structure contains one row for each string, and one column of strings, only the rowCount() function needs to be implemented; by default, the columnCount() function provided by QAbstractListModel will return a value of 1. The data() function returns the data that corresponds to each model index specified. In this way, the model controls access to the underlying data. We do not need to provide a function to set the data in the model because the string list is supplied to the constructor, and this model is read-only.

These functions are implemented quite simply:

    int StringListModel::rowCount() const
    {
        return stringList.count();
    }

    QVariant StringListModel::data(const QModelIndex &index, int role) const
    {
        if (!index.isValid())
            return QVariant();

        if (role == DisplayRole)
            return stringList.at(index.row());
        else
            return QVariant();
    }

The data() function returns a QVariant containing the information referred to by the model index. If the view requests an item from the string list, this is returned to the view, but only after a number of checks are performed on the model index. If the view specified an invalid model index, the model indicates this by returning an invalid QVariant.

Vertical and horizontal headers are supplied by the headerData() function. In this model, the value returned for these items is the row or column number, depending on the header:

    QVariant StringListModel::headerData(int section, Qt::Orientation orientation,
                                    int role) const
    {
        if (role != DisplayRole)
            return QVariant();

        if (orientation == Qt::Horizontal)
            return QString("Column %1").arg(section);
        else
            return QString("Row %1").arg(section);
    }

We only include an excerpt from the main() function for this example:

        QStringList numbers;
        numbers << "One" << "Two" << "Three" << "Four" << "Five";

        QAbstractItemModel *model = new StringListModel(numbers);
        QListView *view = new QListView();
        view->setWindowTitle("View onto a string list model");
        view->setModel(model);

We created a string list to use with the model, and this is supplied to the model when it is constructed. The information in the string list is then made available to the view via the model.

If we require an editable model, we only need to define the following functions in the class declaration:

        ItemFlags flags(const QModelIndex &index) const;
        bool setData(const QModelIndex &index, int role, const QVariant &value);

These are implemented in the following way:

    QAbstractItemModel::ItemFlags StringListModel::flags(const QModelIndex &index) const
    {
        if (!index.isValid())
            return ItemIsEnabled;

        return ItemIsEnabled | ItemIsSelectable | ItemIsEditable;
    }

    bool StringListModel::setData(const QModelIndex &index, int role,
                                  const QVariant &value)
    {
        if (index.isValid() && role == EditRole) {

            stringList.replace(index.row(), value.toString());
            emit dataChanged(index, index);
            return true;
        }
        return false;
    }

In the flags() function, we ensure that every item in the model is editable by returning the same combination of values for all indexes. The setData() function updates the data in the string list, but only if the specified index refers to an item that is valid, is enabled, and is editable. The dataChanged() signal ensures that the view is kept up-to-date.

This example shows that it can be easy to populate views with data from a simple model. The standard models and views planned for Qt 4 will make the process even easier.

What's Changed Since Qt 4 Technology Preview 1?

Convenience classes that correspond to Qt 3's item view classes are now included with the model/view classes. These provide a familiar item-based interface for developers who do not need the flexibility of the pure model/view classes. We introduce the QListWidget, QTreeWidget, and QTableWidget classes. These correspond to the QListBox, QListView, and QTable classes from Qt 3.

After feedback from the Qt 4 preview feedback mailing list and some internal discussion, the concept of a model index type was abandoned in favor of a separate mechnism for supplying header data from models. This separates the task of providing headers to views from the more common task of supplying data to views, making the whole process more efficient.

The delegate classes have been simplified so that they are easier to subclass, and to make it easier to implement new editor widgets for delegates.

What's Changed Since Qt 3?

The table and item view classes in Qt 3 implemented widgets that served both as containers and as views onto their contents. These classes were designed to be easy-to-use and consistent. The equivalent classes in Qt 4 are designed to be extensible while remaining easy-to-use; the introduction of the model/view architecture ensures that they will be more consistent than their predecessors. The view classes provided can be summarized in the following way:

Since the model takes responsibility for managing items, and the view takes care of their presentation to the user, we do not require classes to represent individual items. As a result, there are no equivalent classes to the item classes provided by Qt 3 such as QListViewItem, QTableItem, and QIconViewItem. The delegate now handles the painting and editing of items from the model, and selections of items are stored in selection ranges.

For developers who wish to continue to use old-style item views, there are a number of convenience classes that will provide familiar interfaces without requiring the use of compatibility classes. The QListView class provides a widget to display a list of items, as found in Qt 3's QListBox class, and the QTreeView class implements the equivalent of Qt 3's QListView class. User's of Qt 3's QTable class will find that QTableView provides comparable functionality with a familiar interface.

The move towards a model/view architecture presents both challenges and opportunities for developers. Although the approach may appear to be rather powerful for simple applications, it encourages greater reuse of components, both within the library and within applications. For example, QFileDialog now displays the contents of a QDirModel using the QListView and QTreeView widgets.

Future Work

We plan to introduce the following features in the Qt 4 release:

[Back to the Technology Preview page]


Copyright © 2004 Trolltech. Trademarks
Qt 4.0.0-tp2