Have you ever heard of Model-View-Controller, or MVC for short? If not - not to worry - as you might have never worked with a GUI toolkit before. But perhaps you've heard of the SoC principle, i.e. Separation of Concerns? Essentially MVC is a design pattern used in GUI applications which follows the SoC principle by isolating and decoupling different components. In other words, each component: the Model, the View and the Controller are addressing different concerns, which give developers a greater flexibility and reusage. MVC originates from smalltalks, and was formulated by Trygve Reenskaug. Although the design pattern is rather old, I dare say that most GUI toolkits rely on a variant of it, such as MVP and MVVM. Qt's MVC variant is called the Model/View which plays a central role in any data-driven Qt application. This post will discuss the importance of this pattern and how it's implemented in the Qt framework. This is the fifth post in the series "Crash course in Qt for C++ developers" covering the MVC or Model/View programming in Qt. The other topics are listed below. Perhaps you didn't understand any of that. That's absolutely fine! We're just about to explore what it is and why it's useful. Let's start with the purpose - what problem is MVC solving? Obviously, a non-trivial application needs a good architecture to be scalable. And many good architectures are fundamentally based on the SoC principle. By incorporating MVC, the programs are more adaptable to an ever changing specification and can easily be tested and extended with additional functionality. These capabilities are core ingredients in a good architecture formula. Let's see what those concerns are in the MVC pattern. The three components consist of: By using this separation, it's easy to change the presentation and the visuals without changing the underlying logic. It's also simple to write unit tests targeting the logic without involving the UI. Another benefit of this design is that the same model can be used for multiple views simultaneously without synchronising data. Note that there are many variants of the MVC architecture which can be significantly different from the aforementioned description. Some GUI frameworks even use MVC as the core architecture for the whole application whereas in Qt, it is only used to present one data structure. A typical example is to visualise data from a database in a table. Let's explore how that works. Similarly to MVC, Qt's Model/view design pattern is essentially separated into three components: You might wonder where MVC's Controller comes in, in this design. The controller is essentially merged into the view and the delegate of the model/view design as they are both receiving user inputs. It's also worth pointing out that both the delegate and the view in Qt's design act as the view in the traditional MVC description: they are both responsible for the presentation of the elements. Qt's design is quite different from MVC but the core concept is still there, the representation of the data is separated from the presentation of it. This might still be very confusing, and is perhaps best understood with a simple example. Before we jump into the example below, I want to point out that it specifically covers model/view using the Qt Widgets module. That said, similar behaviour and techniques can be done by using the Qt Quick module and QML. By focussing on Qt Widgets, I believe the concepts discussed here will be easier to grasp for someone who's coming from a C++ background. However, this tutorial from Qt and this chapter from the QML book are great reads should you be interested in using model/view with Qt Quick. OK, let's now start with the code. The example creates two presentations using one set of data. The QSplitter is not part of the model/view pattern but is used here to display the two views at the same time. The two presentations, or rather, the views are: Those views are among some of the ready-made ones provided by Qt. Some other commonly used classes are QTableView and QTreeView. They are each based on the QAbstractItemView abstract base class. The Imagine you would now edit one of the elements in the list. What do you think would happen to the data in the combo box? Let's give it a go! In order to edit one of the elements, select it in the list and start typing. We have now changed the data in the model used by the Great! It has automatically detected that updates have been made to the data and that the Similar to the views, Qt provides some ready-made models. The model we are using in this example is one of the simplest ones: the QStringListModel which obviously only stores a list of strings. There are many more models in Qt, such as the QFileSystemModel, the QSqlTableModel and the QStandardItemModel. I believe both The This model is useful when only a few elements are needed, but will perform quite badly with a large dataset. The main reasons for this are that each element is dynamically allocated individually and the whole dataset is usually created even though only a handful are displayed at the same time. If you're using a big dataset, you'll most likely want to subclass QAbstractItemModel or any of the other convenient classes, such as QAbstractListModel and QAbstractTableModel. Subclassing one of those classes is a big subject on its own and won't be covered in this post. However, as usual, Qt provides good documentation which covers this. We have now talked about the basics regarding the view and the model. But what happened to the delegate? You might have noticed that the basic example doesn't include a delegate. However, there is actually a default delegate attached to the To add the character icon we'll need to override the paint() and the sizeHint() functions. For more information on how this can be done, see this example provided by Qt. Similarly, the functions createEditor(), setEditorData(), setModelData() and lastly updateEditorGeometry() are typically used when custom editing is required. Also, in this case, Qt has got you covered with an example. I fully understand if this feels a bit overwhelming and a lot to take in. Model/view requires some time to get used to in order to fully understand the different concepts. In addition to this, it's also quite easy to get it wrong when you're implementing your own model. If you're interested in doing just that, I would suggest to have a look at this post by KDAB as it covers some of the problems that you might encounter along the way. Lastly, I strongly recommend to also go through the official tutorial by Qt. Good luck!
What? Why?
Qt's Model/view
Basic example
int main(int argc, char *argv[]) {
QApplication app{argc, argv};
QSplitter splitter{Qt::Vertical};
auto model = new QStringListModel{&app};
model->setStringList(QStringList{"Gandalf", "Aragorn", "Legolas", "Samwise Gamgee" ,"Gimli", "Bilbo Baggins", "Peregrin Took", "Boromir"});
auto combo_box_view = new QComboBox{&splitter};
combo_box_view->setModel(model);
auto list_view = new QListView{&splitter};
list_view->setModel(model);
splitter.show();
return app.exec();
}
The View
QAbstractItemView
is essentially an interface that can be subclassed and utilised in order to detect when changes are made to the data in the model. The communication between the view and the model is done using the signals and slots mechanism which enables the model to be fully decoupled from the view. This mechanism was previously covered in another post in the series. QListView
. In this instance, I've changed Gandalf to Gandalf the White and Boromir to Faramir. By selecting the combo box we can now inspect all the elements:
QComboBox
has been notified accordingly. This works because the model is shared across both views.The model
QFileSystemModel
and QSqlTableModel
are pretty self-explanatory, however the QStandardItemModel
is a bit more interesting to discuss. QStandardItemModel
is a generic model where custom data can be stored. Each element corresponds to an item which is created individually. By using this model it's easy to represent a simple tree, table or list structure e.g:static const constexpr int kTableSize{2};
// Create a table showing the OR states for two binaries
QStandardItemModel or_table_model{kTableSize, kTableSize};
for (auto row{0}; row < kTableSize; ++row) {
for (auto column{0}; column < kTableSize; ++column) {
auto or_gate = row || column ? "y" : "n";
auto item = new QStandardItem{QString{or_gate}};
or_table_model.setItem(row, column, item);
}
}
The delegate
QListView
. That's, for example, why it's possible to edit one of the elements in the list. But what if we would like to customise the look and the behaviour when editing? Perhaps instead of just displaying the names in the list, we would also want to display a small icon showing each movie character. And perhaps instead of allowing each element to be changed to any text, we could limit the editing to a list of available characters. This can be done by customising the delegate. To customise the delegate you'll need to subclass a QAbstractItemDelegate or any of the convenient classes QStyledItemDelegate or QItemDelegate. A typical delegate subclass looks like this:class MyDelegate: public QStyledItemDelegate
{
Q_OBJECT
public:
MyDelegate(QObject* parent = nullptr);
// Typically these two functions are needed to override if custom display rendering is desired
void paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const override;
QSize sizeHint(const QStyleOptionViewItem& option,
const QModelIndex& index) const override;
// Typically these four functions are needed to override if custom editing is desired
QWidget *createEditor(QWidget* parent, const QStyleOptionViewItem& option,
const QModelIndex &index) const override;
void setEditorData(QWidget* editor, const QModelIndex& index) const override;
void setModelData(QWidget* editor, QAbstractItemModel* model,
const QModelIndex& index) const override;
void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option,
const QModelIndex& index) const override;
};
Still Confused?