Qt Model/View Programming Tutorial

In Qt it is extremely useful to understand how to use Qt’s Model/View architecture, which is provided by Qt through views, models, and delegates. In this tutorial, we will be going through creating a customizable table model that will contain data that is organized into rows and may be edited. This data organization would be best used for data in which the rows do not interact and are not directly related, but data within the row itself is interconnected. An example of this would be a table in which you display the name of a person in the first column, and a related number in the second column. If you want to go through the completed code, it is available on github.

To accomplish this, we will first go through some sections of Qt’s introduction to model/view programming, specifically the ones related to views and models and then begin to create the model. I will assume that you understand c++ syntax, but I do not make any assumptions about Qt knowledge.

Qt gives a good explanation of what a model view architecture is here. Model/view classes belong to three different groups: view, models, and delegates. The view presents data, handles navigation between items, and is involved in some aspects of item selection. The model provides access to the data that will be displayed by the view, and is also responsible for updating any changes made to the data. Views will, by default, provide basic editing for items but by using a delegate you can customize the editing further. In this tutorial I will not be using a delegate, but I show how to set up a delegate in my next post. Please note that I will be using the c++11 features unique_ptr: if you do not have access to unique_ptr, then you will want to just store a regular pointer, and free this memory in your deconstructors. I will be using and referring to Qt 5.3.

Item based Programming vs Model based Programming

Within this tutorial we focus on how to set up a model based view. However, it is also possible to use one of the views that are item based. The main difference between item and model programming is how data is stored: in an item based view, the data used by the view is stored by the view itself while in model based programming the view queries the model for the data. Both styles have advantages and disadvantages.

One of the advantages of an item based approach is that it is very easy to set up: unlike pure model based, all of the item based views are already implemented (although you can still customize them). Unfortunately, this advantage also means that the item based views are not as extendable, but they are still very useful.

The main disadvantage of item based is that if you want the data to be edited the data will be stored in two different locations (the view, and your own classes) and so it can get very tricky to keep both of them updated. However, with the model based architecture, the view will get its data from the model, and so there is only one place where the data will need to be updated: the model.

A minor advantage of model based is that you can have multiple views displaying the same data from a model, and changes in one view will be reflected in all of the others. It would be possible to accomplish this through just item based, but it would not be easy.

If you know of any additional advantages or disadvantages for the architectures, please comment below.

Views

The purpose of a regular view is to present the data contained in the model to the user. Furthermore, views handle navigation between data, provide some aspects of data selection, and provide default editing for data. All view classes inherit from QAbstractItemView. There are 3 major built-in model based views: QListView, QTableView, and QTreeView.

To use these classes you will need to use, or create, a model which derives from QAbstractItemModel as the model for the view. You can use a single model for multiple views, even among different types of views (although our model will be specialized for use with QTableView). It is also possible to create your own custom view, although I will not be covering that. See here for an example of how to create a custom view that is a subclass of QAbstractItemView.

Within our implementation we will be using a basic QTableView, which is the view best suited for displaying rows of data.

Item based Views

There are also additional view classes: QListWidget, QTableWidget, and QTreeWidget. These views are item based, rather than model based.

These views are easier to set up and use, but they are not as flexible and are slightly slower with larger data sets. I will not be covering how to use them, but there are basic examples available for QListWidget, QTreeWidget, and QTableWidget available.

Models

The general purpose of a model is to store the data for use by the view. How the model stores the data can vary, from string the data in a vector to even querying a server for the requested data. All models will inherit from QAbstractItemModel. Qt provides many pre-built models, including QStandardItemModel and QFileSystemModel.

In this post I will not be covering sorting items within a model, nor will I be sorting Drag and Drop functionality, although I may cover both topics in the future.

Choosing a Model Implementation

If you want a model that works for QTreeView, or if you want a model that works for multiple view types, then you will probably want to inherit from QAbstractItemModel (an example can be found here), or use QStandardItemModel. If you only want your model to work for QListView or QTableView, but don’t want to use/inherit from QStandarditemModel, then you would want to inherit from QAbstractListModel or QAbstractTableModel.

QStandardItemModel

The most general pre-built model is QStandardItemModel, which “provides a classic item-based approach to working with the model:” it acts very similar to the view widgets. However, instead of the items being stored in the view, they are now stored in a model so it is easy to display the same data among different views. QStandardItemModel is not as extendable as inheriting from QAbstractItemModel, QAbstractListModel, or QAbstractTableModel and is instead usually used when you don’t want or need to write your own model implementation. An example of how to use a very simple QStandardItemModel with a table, list, and tree view is at http://www.java2s.com/Code/Cpp/Qt/QListViewdemoandQStandardItem.htm.

Implementation

For our example with rows of data, we will be inheriting from QAbstractTableModel, because this best suits our purpose. This is because QStandardItemModel would not easily be able to represent the idea of the data being contained in rows, and since we want to only display the data in a table, using QAbstractItemModel would mean we would be wasting time re-implementing many things that are done is QAbstractTableModel or not taking advantage of existing functionality in QAbstractTableModel. I will be calling the model class MyModel: feel free to use a different name, but I will refer to it as MyModel throughout the rest of this tutorial. The finished code for MyModel is available here for the header, and here for the cpp.

Model Data Storage

We will, of course, need a way to store the actual data for the model. To store the data, we will create a class called DataRow which will act like an interface so our model will be able to store many different row implementations.

The organization of the DataRows will be quite simple: it will be stored in a vector within MyModel, with the position in the vector corresponding to the row that the DataRow will be displayed in.

We will create DataRow after we set up the model, so we have a better idea of what the DataRow will need to be able to do. Because DataRow will be just an interface, we will need to store a pointer to the data. To make memory management easier, I will use std::unique_ptr instead of just a basic pointer.

The complete declaration for DataRow is available here.

Model Display

The required public functions to make a model inheriting from QAbstractTableModel displayable are:

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

You may be wondering what QModelIndex is for. What it does is allow the model or view to determine what data is wanted, while not sharing how the data is actually shared.  To accomplish this, it has column() and row() accessor functions. Furthermore, it is also possible to get the model that the QModelIndex is from, using the model() function. More details are available at http://qt-project.org/doc/qt-5/model-view-programming.html#model-indexes. Just be careful with storing QModelIndex because it is temporary: if you want to store the index you should use QPersistentModelIndex.

The purpose of columnCount and rowCount is really basic (just get the total number of columns/rows in the model), so their code is quite simple:

int MyModel::columnCount(const QModelIndex & /*parent = QModelIndex()*/) const
{
    if (rowsInfo.size() == 0)
        return 0;

    // If you will have rows with different columns this will need to instead be the max value.
    return rowsInfo[0]->GetColumnCount(); 
}
int MyModel::rowCount(const QModelIndex & /* parent = QModelIndex()*/) const
{
    return rowsInfo.size();
}

The data function will return the data that is at the index using QVariant. The code is quite simple, all we need to do is make sure the index is valid, and that the data asked for will be used for a display role or edit role. If it is, then just need to return what the data at that point refers to using the index’s position:

QVariant MyModel::data(const QModelIndex & index, int role) const
{
    if (!IndexIsValid(index))
        return QVariant();

    if (role == Qt::DisplayRole || role == Qt::EditRole)
        return rowsInfo[index.row()]->GetData(index.column());

    return QVariant();
}

The function IndexIsValid is a convenience function that I added to MyModel. It will check to see if the index is valid, and if the row is within the constraints for the model:

bool MyModel::IndexIsValid(const QModelIndex & index) const
{
    return index.isValid() && index.row() < rowCount();
}

A QVariant acts like a union in c++ and is able to store many common Qt data types. A good explanation of what a QVariant is, and how to use one, is available here.

Qt contains many possible roles for item data: for our current implementation, we only care about the DisplayRole and EditRole, although you could easily allow other roles such as ToolTipRole for an enhanced user experience.

Finally, the headerData function will return a QVariant that contains the data that will go for the header at the given orientation and section:

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

    // If you are displaying headers, will want to add more code here
    if (orientation == Qt::Horizontal)
        return QString("Column %1").arg(section);
    else
        return QString("Row %1").arg(section);
}

If you wanted to improve the wording for the headers one possible way to would be to have the DataRow store/generate their corresponding vertical header (row header), and then have either the first DataRow or MyModel store/generate the horizontal headers (column headers). Or MyModel could store/generate both the horizontal and vertical header.

Model editing

Since we will also want to be able to edit the data in MyModel, will need the following additional functions:

Qt::ItemFlags flags(const QModelIndex & index) const override;

bool setData(const QModelIndex & index, const QVariant & value,
    int role = Qt::EditRole) override;

The function flags will add the enum Qt::ItemIsEditable to the flags returned by QAbstractTableModel’s flag function, but only if the data can be edited:

Qt::ItemFlags MyModel::flags(const QModelIndex & index) const
{
    if (!IndexIsValid(index))
        return Qt::ItemIsEnabled;

    if (rowsInfo[index.row()]->CanBeChanged(index.column()))
        return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;

    return QAbstractTableModel::flags(index);

    // Optional setting. 
    // Will make it so user cannot select indexes that can't be changed.
    /*
    if (rowsInfo[index.row()]->;CanBeChanged(index.column()))
        return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;

    // If you don't know what this does, look at http://stackoverflow.com/questions/3920307/how-can-i-remove-a-flag-in-c
    return QAbstractTableModel::flags(index) & ~Qt::ItemIsSelectable;
    */
}

setData will get the DataRow indicated by the index to update its data corresponding to the given column. It will also return true if it changed data.

bool MyModel::setData(const QModelIndex & index, const QVariant & value, int role /*= Qt::EditRole*/)
{
    if (IndexIsValid(index) && role == Qt::EditRole) {
        rowsInfo[index.row()]->DataChanged(index.column(), value);
        emit dataChanged(index, index);
        return true;
    }
    return false;
}

Note the line emit dataChanged(index, index). Whenever the data in the model is changed (and you want to display this change), you NEED to call use that line of code (or code that accomplishes the same purpose): otherwise, the views that are displaying data from that model will not update their display.

Now we have a good idea of what functions the interface DataRow will need to have:

DataRow
class DataRow
{
public:
    virtual ~DataRow() { }

    virtual QVariant GetData(int column) const = 0;

    virtual int GetColumnCount() const = 0;

    virtual void DataChanged(const int & column, const QVariant & value) = 0;

    virtual bool CanBeChanged(const int & column) const = 0;
};

As you can see, DataRow will not implement any of these functions and this makes MyModel very extendable.

As an example, here are the header file and cpp file for a concrete implementation of DataRow called NameNumberInfo, which will display an unchanging name, and a corresponding, and changeable, number.

Now we can now create a QTableView and have it display something useful:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QTableView table;

    // Basic and optional formatting for the table and its headers.
    // table.setShowGrid(false);

    // table.verticalHeader()->setVisible(false);
    // table.horizontalHeader()->setVisible(false);
    // table.horizontalHeader()->setStretchLastSection(true);

    // table.
    std::vector<std::unique_ptr<DataRow> > numbers;
    numbers.push_back(std::unique_ptr<DataRow>(new NameNumberRow("Row 1", 0)));
    numbers.push_back(std::unique_ptr<DataRow>(new NameNumberInfo("Row 2", 1)));

    MyModel model = MyModel(numbers);
    table.setModel(&model);

    table.show();

    return a.exec();
}

Just remember to make sure that neither the table, nor the model, will go out of scope (will not go out of scope in above program because of the return line, which will wait for the table to be closed). If they might, should instead set up the table and the header as pointers to new‘d memory.

In my next tutorial I covered how to set up more advanced editing using a delegate.

I would suggest that you also read the full document on Qt’s Model/View architecture which is here. While it may be quite long, it is very helpful and has extra details that were not important for our implementation. Additionally, many tutorials created by QtProject for view are available at http://qt-project.org/doc/qt-5/examples-itemviews.html.

If you still have any questions, please ask them in the comments. And if you have any suggestions, please comment or email me at mredshaw@diusrex.com.

Categories: c++, model/view programming, qt |

12 thoughts on “Qt Model/View Programming Tutorial

  1. Thank you!! Good tutorials on which is the conceptual framework behind a library like Qt is a must. Some times people spend moths if not years studying a library without grasping the model behind. Keep on writing. Maybe your tutorial is the seed of a much needed book….

    1. @CCMA Glad that it helped! I’m planning on continuing to write tutorials on what I have had difficulties with in Qt, but I don’t have any plans for a book.

  2. Very cool stuff, thanks for writing it up. I have been using QAbstractItemModels, but recently have been wondering why I am not just using QStandardItemModels. The book I use focuses on the former (indeed, it has *zero* discussion of the latter).

  3. So my question is, do you have a good sense of why QStandardItemModel doesn’t seem to be subclassed as often as QAbstractItemModel? I am guilty of this too, but my only excuse is my source material doesn’t teach the former. Not a great reason 🙂

    1. The main reason why QStandardItemModel isn’t subclassed as much is probably because it isn’t as customizable as using QAbstractItemModel is. After all, QStandardItemModel is itself a subclass of QAbstractItemModel.

      The advantage of using QStandardItemModel (not as a subclass) is it means that you don’t need to write your own model implementation.

  4. This has to be the best article I’ve come across outside of QT’s tutorials on Model / View Programming. Thank you !

  5. main.cpp:25: error: use of deleted function ‘MyModel::MyModel(MyModel&&)’
    MyModel model = MyModel(numbers);
    ^

    Quick fix:
    MyModel *model = new MyModel(numbers);
    table.setModel(model);

    1. That’s a weird issue since normally the move constructor should be optimized out. Since you seem to be using C++11, it would probably be better to use

      MyModel model{numbers};

      Since otherwise you will probably be leaking memory.

  6. F:\code\qt\Test_ModelViewEditing\main.cpp:32: error: C2280: “MyModel::MyModel(const MyModel &)”: Try to refer to the deleted function

      1. This is not a smart pointer problem. The problem seems to be in the fact that with a non-trivial constructor MyModel::MyModel(…) given, the default one as well as an assignment operator (and copy constructor) may be absent. The assignment operator is used in the line:
        MyModel model = MyModel(numbers);
        which is also costly – MyModel object is created two times.
        Interesting, that the problem appears when I compile the code on Linux, gcc (SUSE Linux) 4.8.3 20140627 and no problem on Mac, clang-900.0.38.
        It’s cheap and safe to use
        MyModel *model = new MyModel(numbers);
        see my comment above.

      2. Yea thats a smart pointer problem because the default copy constructor can’t exist due to the class containing std::unique_ptr. You can define your own copy constructor, but that shouldn’t be necessary in this case.

        Try changing the code to:
        MyModel model{numbers};

        If this fixes it for you, I’ll change the code in the repository (right now, I can’t compile it myself due to not having qt installed….)

Leave a Reply

Your email address will not be published. Required fields are marked *

[TOP]