Tumblelog by Soup.io
Newer posts are loading.
You are at the newest post.
Click here to check if anything new just came in.
18:57

The RelayCommand in Qt

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()
    }
}
0 Add to favourites0 Bury

Don't be the product, buy the product!

Schweinderl