In the XAML world it’s very common to use the MVVM pattern. I will explain how to use the technique in a similar way with Qt and QML.
The idea is to not have too much code in the view component. Instead we have declarative bindings and move most if not all of our view code to a so called ViewModel. The ViewModel will sit in between the actual model and the view. The ViewModel typically has one to one properties for everything that the view displays. Manipulating the properties of the ViewModel alters the view through bindings. You typically don’t alter the view directly.
In our example we have two list-models, two texts and one button: available-items, accepted-items, available-count, accepted-count and a button. Pressing the button moves stuff from available to accepted. Should be a simple example.
First the ViewModel.h file. The class will have a property for ~ everything the view displays:
#ifndef VIEWMODEL_H
#define VIEWMODEL_H
#include <QAbstractListModel>
#include <QObject>
class ViewModel : public QObject
{
Q_OBJECT
Q_PROPERTY(QAbstractListModel* availableItems READ availableItems NOTIFY availableItemsChanged )
Q_PROPERTY(QAbstractListModel* acceptedItems READ acceptedItems NOTIFY acceptedItemsChanged )
Q_PROPERTY(int available READ available NOTIFY availableChanged )
Q_PROPERTY(int accepted READ accepted NOTIFY acceptedChanged )
public:
ViewModel( QObject *parent = 0 );
~ViewModel() { }
QAbstractListModel* availableItems()
{ return m_availableItems; }
QAbstractListModel* acceptedItems()
{ return m_acceptedItems; }
int available ()
{ return m_availableItems->rowCount(); }
int accepted ()
{ return m_acceptedItems->rowCount(); }
Q_INVOKABLE void onButtonClicked( int availableRow );
signals:
void availableItemsChanged();
void acceptedItemsChanged();
void availableChanged();
void acceptedChanged();
private:
QAbstractListModel* m_availableItems;
QAbstractListModel* m_acceptedItems;
};
#endif
The ViewModel.cpp implementation of the ViewModel. This is of course a simple example. The idea is that ViewModels can be quite complicated while the view.qml remains simple:
#include <QStringListModel>
#include "ViewModel.h"
ViewModel::ViewModel( QObject *parent ) : QObject ( parent )
{
QStringList available;
QStringList accepted;
available << "Two" << "Three" << "Four" << "Five";
accepted << "One";
m_availableItems = new QStringListModel( available, this );
emit availableItemsChanged();
m_acceptedItems = new QStringListModel( accepted, this );
emit acceptedItemsChanged();
}
void ViewModel::onButtonClicked(int availableRow)
{
QModelIndex availableIndex = m_availableItems->index( availableRow, 0, QModelIndex() );
QVariant availableItem = m_availableItems->data( availableIndex, Qt::DisplayRole );
int acceptedRow = m_acceptedItems->rowCount();
m_acceptedItems->insertRows( acceptedRow, 1 );
QModelIndex acceptedIndex = m_acceptedItems->index( acceptedRow, 0, QModelIndex() );
m_acceptedItems->setData( acceptedIndex, availableItem );
emit acceptedChanged();
m_availableItems->removeRows ( availableRow, 1, QModelIndex() );
emit availableChanged();
}
The view.qml. We’ll try to have as few JavaScript code as possible; the idea is that coding itself is done in the ViewModel. The view should only be view code (styling, UI, animations, etc). The import url and version are defined by the use of qmlRegisterType in the main.cpp file, lower:
import QtQuick 2.0
import QtQuick.Controls 1.2
import be.codeminded.ViewModelExample 1.0
Rectangle {
id: root
width: 640; height: 320
property var viewModel: ViewModel { }
Rectangle {
id: left
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: button.top
width: parent.width / 2
ListView {
id: leftView
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: leftText.top
delegate: rowDelegate
model: viewModel.availableItems
}
Text {
id: leftText
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: 20
text: viewModel.available
}
}
Rectangle {
id: right
anchors.left: left.right
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: button.top
ListView {
id: rightView
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: rightText.top
delegate: rowDelegate
model: viewModel.acceptedItems
}
Text {
id: rightText
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: 20
text: viewModel.accepted
}
}
Component {
id: rowDelegate
Rectangle {
width: parent.width
height: 20
color: ListView.view.currentIndex == index ? "red" : "white"
Text { text: 'Name:' + display }
MouseArea {
anchors.fill: parent
onClicked: parent.ListView.view.currentIndex = index
}
}
}
Button {
id: button
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: 20
text: "Accept item"
onClicked: viewModel.onButtonClicked( leftView.currentIndex );
}
}
A main.cpp example. The qmlRegisterType defines the url to import in the view.qml file:
#include <QGuiApplication>
#include <QQuickView>
#include <QtQml>
#include <QAbstractListModel>
#include "ViewModel.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQuickView view;
qRegisterMetaType<QAbstractListModel*>("QAbstractListModel*");
qmlRegisterType<ViewModel>("be.codeminded.ViewModelExample", 1, 0, "ViewModel");
view.setSource(QUrl("qrc:/view.qml"));
view.show();
return app.exec();
}
A project.pro file. Obviously should you use cmake nowadays. But oh well:
TEMPLATE += app
QT += quick
SOURCES += ViewModel.cpp main.cpp
HEADERS += ViewModel.h
RESOURCES += project.qrc
And a project.qrc file:
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource prefix="/">
<file>view.qml</file>
</qresource>
</RCC>