Search code examples
c++qtqmlqtquick2qtquickcontrols

Problems with editing C++ QList<Object*> model in Qml code and some Qml warnings


I need to create a model that can be edited in C++ and Qml code. This model will be used in an desktop application that contains both Qt Widgets and Qml. For qml rendering I use the QQuickWidget.

I have data object with two properties: name and color.

dataobject.h

#ifndef DATAOBJECT_H
#define DATAOBJECT_H

#include <QObject>

class DataObject : public QObject
{
    Q_OBJECT

    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
    Q_PROPERTY(QString color READ color WRITE setColor NOTIFY colorChanged)

public:
    DataObject(QObject *parent = Q_NULLPTR);
    DataObject(const QString &name, const QString &color, QObject *parent = Q_NULLPTR);

    QString name() const;
    void setName(const QString &name);

    QString color() const;
    void setColor(const QString &color);

signals:
    void nameChanged();
    void colorChanged();

private:
    QString m_name;
    QString m_color;
};

#endif // DATAOBJECT_H

dataobject.cpp

#include "dataobject.h"

#include <QDebug>

DataObject::DataObject(QObject *parent)
    : QObject(parent)
{
}

DataObject::DataObject(const QString &name, const QString &color, QObject *parent)
    : QObject(parent), m_name(name), m_color(color)
{
}

QString DataObject::name() const
{
    return m_name;
}

void DataObject::setName(const QString &name)
{
    qDebug() << Q_FUNC_INFO;

    if (name != m_name) {
        m_name = name;
        emit nameChanged();
    }
}

QString DataObject::color() const
{
    return m_color;
}

void DataObject::setColor(const QString &color)
{
    qDebug() << Q_FUNC_INFO;

    if (color != m_color) {
        m_color = color;
        emit colorChanged();
    }
}

For the main window I use subclass of QMainWindow. Сentral widget contains QQuickWidget with source MainView.qml. In constructor I fill QList<Object*> model and set it as context property "nameColorModel" for MainView.qml.

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = Q_NULLPTR);
    ~MainWindow();

public slots:
    void onAccepted();

private:
    QList<QObject*> nameColorModel;
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "dataobject.h"

#include <QQuickWidget>
#include <QQmlContext>
#include <QQuickItem>

#include <QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent)
{
    auto qmlWidget = new QQuickWidget(QUrl("qrc:/MainView.qml"), this);
    qmlWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);

    this->setCentralWidget(qmlWidget);
    this->resize(600, 400);

    nameColorModel.append(new DataObject("Item 1", "red"));
    nameColorModel.append(new DataObject("Item 2", "green"));
    nameColorModel.append(new DataObject("Item 3", "blue"));
    nameColorModel.append(new DataObject("Item 4", "yellow"));

    qmlWidget->rootContext()->setContextProperty("nameColorModel", QVariant::fromValue(nameColorModel));

    connect(qmlWidget->rootObject(), SIGNAL(accepted()), SLOT(onAccepted()));
}

MainWindow::~MainWindow()
{
    qDeleteAll(nameColorModel.begin(), nameColorModel.end());
}

void MainWindow::onAccepted()
{
    for(auto& object: nameColorModel)
    {
        auto item = qobject_cast<DataObject*>(object);
        qDebug() << item->name() << item->color();
    }
}

MainView.qml contains some additional components (firstTextField, secondComboBox, "Ok" and "Cancel" buttons) and GroupBox containing Repeater which uses my "nameColorModel". NameColorEdit.qml is used as delegate of Repeater.

MainView.qml

import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1

import "." as Views

Item {
    id: root
    width: 600
    height: 400

    property alias firstText: firstTextField.text
    property alias secondText: secondComboBox.currentText

    signal accepted()
    signal rejected()

    ColumnLayout {
        spacing: 10
        anchors.fill: parent
        anchors.margins: 10

        GridLayout {
            columns: 2
            rowSpacing: 10
            columnSpacing: 10

            Label {
                text: "First"
            }

            TextField {
                id: firstTextField
                implicitHeight: 42
                Layout.fillWidth: true
            }

            Label {
                text: "Second"
            }

            ComboBox {
                id: secondComboBox
                implicitHeight: 42
                model: 5
                Layout.fillWidth: true
            }
        }

        GroupBox {
            title: qsTr("Name-color objects:")
            Layout.fillWidth: true

            ColumnLayout {
                id: col
                spacing: 10
                anchors.fill: parent

                Repeater {
                    id: repeater
                    model: nameColorModel ////  <-- QList<Object*> model

                    Views.NameColorEdit {
                        name: modelData.name
                        color: modelData.color
                        Layout.row: index
                        Layout.fillWidth: true
                    }
                }
            }
        }

        Item {
            Layout.fillHeight: true
        }

        RowLayout {
            Layout.alignment: Qt.AlignRight

            Button {
                text: "Ок"
                Layout.minimumWidth: 42
                Layout.minimumHeight: 42
                onClicked: accepted()
            }

            Button {
                text: "Cancel"
                Layout.minimumWidth: 42
                Layout.minimumHeight: 42
                onClicked: rejected()
            }
        }
    }
}

NameColorEdit.qml

import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1

Item {
    id: root
    implicitWidth: nameField.implicitWidth
    implicitHeight: nameField.implicitHeight

    property alias name: nameField.text
    property alias color: colorField.text

    RowLayout {
        spacing: 10
        anchors.fill: parent

        Label {
            text: "Color"
        }

        TextField {
            id: colorField
            enabled: false
            implicitWidth: 150
            implicitHeight: 42
        }

        Label {
            text: "Name"
        }

        TextField {
            id: nameField
            implicitHeight: 42
            Layout.fillWidth: true
        }
    }
}

When I change text in the "nameField" of NameColorEdit.qml, "nameColorModel" doesn't change in С++ code. How can I fix this?

Also there are following warnings in qml code:

qrc:/MainView.qml:50:9: QML GroupBox: Binding loop detected for property "implicitWidth" qrc:/MainView.qml:61: ReferenceError: nameColorModel is not defined

Note that the model will be set after calling setSource of QQuickWidget. How can I fix these warnings?

You can also give me recommendations on writing code.

Thank you!


Solution

  • The problem was solved by using Binding for DataObject property and NameColorEdit property:

            Repeater {
                id: repeater
                model: nameColorModel ////  <-- QList<Object*> model
    
                Views.NameColorEdit {
                    name: modelData.name
                    color: modelData.color
                    Layout.row: index
                    Layout.fillWidth: true
    
                    Binding { target: modelData; property: "name"; value: name }
                    Binding { target: modelData; property: "color"; value: color }
                }
            }
    

    Now, when editing nameField in NameColorEdit.qml content of QList<Object*> model in C++ code is successfully updated. Also, If we change content of QList<Object*> model in C++ code, NameColorEdit.qml will be updated.