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>