View, edit and update data (from C++ ) in QML with multiple views, while the Data stays in C++ (subscribe to data)

StackOverflow https://stackoverflow.com/questions/19841328

  •  29-07-2022
  •  | 
  •  

Question

I have some data stored in instances of a C++ class (Data.cpp). Now i want to be able to view and edit this data from 2 seperate representations in QML, so that if the values in View1 are changed, the data itself (C++) is changed and the value displayed by View2 as well (because it gets notified when the C++ data changes).

Here is what I got so far:

Data.h

class Data : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)

public:
    Data(std::string name);
    QString name();
    void setName(const QString &n);

signals:
    void nameChanged();

private:
    std::string _name;
};

Parser.h (provides a list of Data)

class Parser : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QList<QObject*> list READ list NOTIFY listChanged)
    //QList<Data*> is not working with QML :(

public:
    Parser(QObject *parent = 0);
    QList<QObject*> list() //stuff below is implementd in Parser.cpp
    {
       _list.append(new Data("name 1"));
       _list.append(new Data("name 2"));
       _list.append(new Data("name 3"));
        return _list;
    }


signals:
    void listChanged();

private:
    QList<QObject*> _list;
};

QML part:

    ListView
    {
        id: view1
        anchors.fill: parent
        spacing: 5
        delegate: Text { text: name}
        model: ListModel{Component.onCompleted: getModel()}
    }
    ListView
    {
        id: list2
        anchors.fill: parent
        spacing: 5
        delegate: Text { text: name}
        model: ListModel{Component.onCompleted: getModel()}
    }
    function getModel()
    {
        var m = parser.list;
        for(var i=0; i<m.length; i++)
        {
            list.model.append(m[i]); //simply returning the list (m) does not work
        }
    }

Now if I click on an item in view1 (for example) i want the name of the corresponding Data to change, and the name displayed in view2 accordingly. If I modified the name from C++, the new name should be displayed in both views.

Is there any way to do this? I'm stuck on this for days... Thanks for your help.


EDIT:

I asked a more specific question to this topic here.

Was it helpful?

Solution

It's very much possible, but you've got a few problems:

  1. You want to expose your list of Data objects as a QQmlListProperty. This is the proper way to get lists into QML
  2. If your list is properly exposed as a QQmlListProperty you can just set it as the model of your ListView without doing that weird getModel() hack that you're doing now
  3. You shouldn't be added items to your list in the getter or else you'll be appending to it every time QML tries to read it.

Once that's resolved, you can update a Data object simply by interacting with the reference to the current model item in your delegate.

Here's a full working example. I've added a MouseArea in the QML that changes the model and also a timer in C++ that also changes the model, to show that changes to either side are instantly reflected in the UI.

main.cpp:

#include <QGuiApplication>
#include <QtQuick>

class Data : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)

public:
    Data(const QString &n) : _name(n) { }
    QString name() const { return _name; }

    void setName(const QString &n) {
        if (_name == n)
            return;
        _name = n;
        emit nameChanged(n);
    }

signals:
    void nameChanged(const QString &n);

private:
    QString _name;
};

class Parser : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QQmlListProperty<Data> list READ list CONSTANT)

public:
    Parser(QObject *parent = 0) {
        _list.append(new Data(QStringLiteral("name 1")));
        _list.append(new Data(QStringLiteral("name 2")));
        _list.append(new Data(QStringLiteral("name 3")));

        startTimer(5000);
    }

    QQmlListProperty<Data> list() {
        return QQmlListProperty<Data>(this, _list);
    }

    void timerEvent(QTimerEvent *) {
        _list[1]->setName(_list[1]->name() + QStringLiteral("C++"));
    }

private:
    QList<Data*> _list;
};

int main(int argc, char *argv[])
{
    QGuiApplication a(argc, argv);

    qmlRegisterType<Data>();

    QQuickView view;
    view.rootContext()->setContextProperty(QStringLiteral("parser"), new Parser);
    view.setSource(QUrl("qrc:///qml/main.qml"));
    view.showNormal();

    return a.exec();
}

#include "main.moc"

main.qml:

import QtQuick 2.0

Rectangle {
    width: 800
    height: 600

    ListView {
        id: view1
        anchors { top: parent.top; left: parent.left; bottom: parent.bottom }
        width: parent.width / 2
        spacing: 5

        delegate: Item {
            height: 30
            width: parent.width

            Text { text: name }

            MouseArea {
                anchors.fill: parent
                onClicked: model.name += "1";
            }
        }
        model: parser.list
    }

    ListView {
        id: view2
        anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
        width: parent.width / 2
        spacing: 5

        delegate: Item {
            height: 30
            width: parent.width

            Text { text: name }

            MouseArea {
                anchors.fill: parent
                onClicked: model.name += "2";
            }
        }
        model: parser.list
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top