A few days ago I explained how we can do MVVM techniques like ICommand in Qt.
Today I’ll explain how to make and use a simple version of the, in the XAML MVVM world quite famous, RelayCommand. In the Microsoft Prism4 & 5 world this is DelegateCommand. Both are equivalent. I will only show a non-templated RelayCommand, so no RelayCommand<T> for now. Perhaps I’ll add a templated one to that mvvm project some other day.
What people call a delegate in C# is what C++ people call a Functor. Obviously we will use functors, then. Note that for people actually reading all those links: in C# the Action<T> and Func<T,G> are basically also C# delegates (or, functors, if you fancy C++’s names for this more).
Here is the RelayCommand.h:
#include <functional> #include <QSharedPointer> #include <MVVM/Commands/AbstractCommand.h> class RelayCommand : public AbstractCommand { Q_OBJECT public: RelayCommand(std::function<void()> executeDelegatep, std::function<bool()> canExecuteDelegatep, QObject *parent = 0) : AbstractCommand(parent) , executeDelegate(executeDelegatep) , canExecuteDelegate(canExecuteDelegatep) {} void execute() Q_DECL_OVERRIDE; bool canExecute() const Q_DECL_OVERRIDE; public slots: void evaluateCanExecute(); private: std::function<void()> executeDelegate; std::function<bool()> canExecuteDelegate; };
The implementation is too simple to be true:
#include "RelayCommand.h" bool RelayCommand::canExecute() const { return canExecuteDelegate(); } void RelayCommand::evaluateCanExecute() { emit canExecuteChanged( canExecute() ); } void RelayCommand::execute() { executeDelegate(); }
Okay, so how do we use this? First we make a ViewModel. Because in this case we will define the command in C++. That probably means you want a ViewModel.
I added a CompositeCommand in the mix. For a Q_PROPERTY isn’t a CommandProxy really needed, as ownership stays in C++ (when for example you pass this as parent). For a Q_INVOKABLE you would need it to wrap the QSharedPointer<AbstractCommand>.
Note. I already hear you think: wait a minute, you are not passing this to the QObject’s constructor, it’s not a QScopedPointer and you have a new but no delete. That’s because CommandProxy converts the ownership rules to QQmlEngine::setObjectOwnership (this, QQmlEngine::JavaScriptOwnership) for itself. I don’t necessarily recommend its usage here (for it’s not immediately clear), but at the same time this is just a demo. You can try printing a warning in the destructor and you’ll see that the QML garbage collector takes care of it.
#include <QObject> #include <QScopedPointer> #include <MVVM/Commands/CommandProxy.h> #include <MVVM/Commands/CompositeCommand.h> #include <MVVM/Commands/RelayCommand.h> #include <MVVM/Models/CommandListModel.h> class ViewModel: public QObject { Q_OBJECT Q_PROPERTY(CommandProxy* helloCommand READ helloCommand CONSTANT) public: ViewModel(QObject *parent=0):QObject(parent), helloCmd(new CompositeCommand()){ QSharedPointer<CompositeCommand> cCmd = helloCmd.dynamicCast<CompositeCommand>(); cCmd->add( new RelayCommand ([=] { qWarning() << "Hello1 from C++ RelayCommand"; }, [=]{ return true; })); cCmd->add( new RelayCommand ([=] { qWarning() << "Hello2 from C++ RelayCommand"; }, [=]{ return true; })); proxyCmd = new CommandProxy (helloCmd); } CommandProxy* helloCommand() { return proxyCmd; } private: QSharedPointer<AbstractCommand> helloCmd; CommandProxy *proxyCmd; };
Let’s also make a very simple View.qml that uses the ViewModel
import QtQuick 2.3 import QtQuick.Window 2.0 import QtQuick.Controls 1.2 import Example 1.0 Item { property ViewModel viewModel: ViewModel {} Button { enabled: viewModel.helloCommand.canExecute onClicked: viewModel.helloCommand.execute() } }