1
votes

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!

1
Your view is created before the context property is set, so the first run will complain about nameColorModel. Try setting the contextproperty earlier.arynaq
You might also want to consider to have a "data handler" class that manages your value pairs and provides signals for change notification and use that in a table model for QtWidgets and a list model for QtQuick. Alternatively a table model for both cases but which maps the QML properties into columnsKevin Krammer
@arynaq, Is there a way to set context properties after view creation without warnings?user7655285
@Kevin Krammer, Thanks for the recommendation!user7655285

1 Answers

1
votes

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.