How to implement WPF-like MVVM in Qt/C++/QML?

17,501

Solution 1

I've posted a rather complete example in another answer.

The general procedure is:

  1. Create a model that derives from QAbstractItemModel. You can reuse any of the models already provided by Qt, for example QStringListModel.

  2. Expose it to QML. E.g. use setContextProperty() of QML Engine's rootContext().

  3. The model's roles are visible in the context of the delegate in QML. Qt provides default mapping between names and roles for the DisplayRole (display) and EditRole (edit) in a default implementation of roleNames().

    delegate: Component {
        TextInput {
            width: view.width // assuming that view is the id of the view object
            text: edit // "edit" role of the model, to break the binding loop
            onTextChanged: model.display = text // "display" role of the model
        }
    }
    
  4. You can create intermediate viewmodels, if needed, by attaching proxy models between the views and the backend models. You can derive from QAbstractProxyModel or one of its subclasses.

Solution 2

The accepted answer is correct save for one detail. In MVVM you'd expose the ViewModel to QML, not the Model.

Share:
17,501

Related videos on Youtube

johnildergleidisson
Author by

johnildergleidisson

Updated on October 10, 2022

Comments

  • johnildergleidisson
    johnildergleidisson over 1 year

    I'm writing a proof of concept application, that is very simple. Basically it's composed of a UI where a list of "Note" type objects is displayed in a QML ListView.

    I then have a few classes which is something along the lines:

    #ifndef NOTE_H
    #define NOTE_H
    
    #include <string>
    
    using namespace std;
    class Note
    {
    public:
        Note(QObject* parent = 0)
            : QObject(parent)
        {
    
        }
    
        Note(const int id, const string& text)
            : _id(id), _text(text)
        {
        }
    
        int id()
        {
            return _id;
        }
    
        const string text()
        {
            return _text;
        }
    
        void setText(string newText)
        {
            _text = newText;
        }
    
    private:
        int _id;
        string _text;
    };
    
    #endif // NOTE_H
    

    Then a repository:

    class NoteRepository : public Repository<Note>
    {
    public:
        NoteRepository();
        ~NoteRepository();
    
        virtual shared_ptr<Note> getOne(const int id);
        virtual const unique_ptr<vector<Note>> getAll();
        virtual void add(shared_ptr<Note> item);
    private:
        map<int, shared_ptr<Note>> _cachedObjects;
    };
    

    Finally a ViewModel that exposes Note to QML

    class MainViewModel : public QObject
    {
        Q_OBJECT
        Q_PROPERTY(QQmlListProperty<Note> notes READ notes NOTIFY notesChanged)
        Q_PROPERTY(int count READ count() NOTIFY countChanged)
    public:
        MainViewModel(QObject *newParent = 0);
        int count();
        QQmlListProperty<Note> notes();
    signals:
        void notesChanged();
        void countChanged();
    public slots:
    private:
        std::shared_ptr<UnitOfWork> _unitOfWork;
        static void appendNote(QQmlListProperty<Note> *list, Note *note);
        QList<Note*> _notes;
    };
    

    PLEASE DON'T MIND ANY C++ MISTAKES HERE and mind they are incomplete, it's not the point at this moment as I'm constantly adapting this as I learn.

    The point where I'm struggling is, how to expose a list-like object to QML? The requirement is this list must be dynamic, one should be able to add, delete and modify the text of a note. When the list is modified by C++, it should also notify the UI (signal).

    I tried QQmlListProperty, but couldn't figure a way of exposing it to QML. Then I read on another SO post this type can't be modified by QML (??), and I stumbled upon QAbstractItemModel.

    Anyhow, can anyone point me to the right direction?