How to Use a Custom Class in C++ Model and QML View: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
m (same body for age in the pre and post Q_GADGET versions)
m (Change code to nowiki tags for formatting)
 
Line 4: Line 4:
=== Letting QML use your custom class  ===
=== Letting QML use your custom class  ===
Say we have a pretty simple custom class, 3 members with getter and setter functions plus a const method that does a simple calculation
Say we have a pretty simple custom class, 3 members with getter and setter functions plus a const method that does a simple calculation
<code>
  <nowiki>
#include <QString>
#include <QString>
#include <QDate>
#include <QDate>
Line 35: Line 35:
     QString m_surname;
     QString m_surname;
};
};
</code>
  </nowiki>


The first thing we need to do is allow QML to interact with this class. To achieve this we'll make it a <tt>Q_GADGET</tt>.
The first thing we need to do is allow QML to interact with this class. To achieve this we'll make it a <tt>Q_GADGET</tt>.
A <tt>Q_GADGET</tt> is a lighter version of <tt>Q_OBJECT</tt>: it doesn't allow you to use signals, slots and the parent/child ownership system but allows you to use <tt>Q_PROPERTY</tt> and <tt>Q_INVOKABLE</tt>.
A <tt>Q_GADGET</tt> is a lighter version of <tt>Q_OBJECT</tt>: it doesn't allow you to use signals, slots and the parent/child ownership system but allows you to use <tt>Q_PROPERTY</tt> and <tt>Q_INVOKABLE</tt>.


<code>
  <nowiki>
#include <QString>
#include <QString>
#include <QDate>
#include <QDate>
Line 77: Line 77:
     QString m_surname;
     QString m_surname;
};
};
</code>
  </nowiki>


Here we declared 4 properties of the object, 3 for the members and one for the age calculation.
Here we declared 4 properties of the object, 3 for the members and one for the age calculation.
Line 83: Line 83:
=== Using your class in a C++ model  ===
=== Using your class in a C++ model  ===
This part is straightforward, we'll build a <tt>QObject</tt> that fills a list model with dummy data and exposes it in a way QML can manage
This part is straightforward, we'll build a <tt>QObject</tt> that fills a list model with dummy data and exposes it in a way QML can manage
<code>
  <nowiki>
#include <QStandardItemModel>
#include <QStandardItemModel>
#include "person.h"
#include "person.h"
Line 115: Line 115:
     QAbstractItemModel* m_model;
     QAbstractItemModel* m_model;
};
};
</code>
  </nowiki>
We expose the model through a property and we also added a slot to add a new person to the list. We used <tt>QStandardItemModel</tt> here just for convenience.
We expose the model through a property and we also added a slot to add a new person to the list. We used <tt>QStandardItemModel</tt> here just for convenience.


=== Passing to QML  ===
=== Passing to QML  ===
The main function is very simple, the only change we made to QtCreator's default is creating the <tt>People</tt> object and passing it to the root context of QtQuick
The main function is very simple, the only change we made to QtCreator's default is creating the <tt>People</tt> object and passing it to the root context of QtQuick
<code>
  <nowiki>
#include <QGuiApplication>
#include <QGuiApplication>
#include <QQmlContext>
#include <QQmlContext>
Line 139: Line 139:
     return app.exec();
     return app.exec();
}
}
</code>
  </nowiki>


=== Using the data in QML  ===
=== Using the data in QML  ===
We'll display the data in a ListView  
We'll display the data in a ListView  
<code>
  <nowiki>
import QtQml 2.2
import QtQml 2.2
import QtQuick 2.7
import QtQuick 2.7
Line 221: Line 221:
     }
     }
}
}
</code>
  </nowiki>
The first thing to notice is that we used the model property from People as the <tt>ListView</tt>'s model.
The first thing to notice is that we used the model property from People as the <tt>ListView</tt>'s model.
Now let's focus on the delegate, as you can see accessing our data is very easy we just use the <tt>roleName.propertyName</tt> syntax. Since we saved our data in <tt>Qt::EditRole</tt> we used <tt>edit</tt> as role name (a list of the default role names is [http://doc.qt.io/qt-5/qabstractitemmodel.html#roleNames In <tt>QAbstractItemModel</tt>'s documentation]) and then accesses directly all the properties of <tt>Person</tt> directly
Now let's focus on the delegate, as you can see accessing our data is very easy we just use the <tt>roleName.propertyName</tt> syntax. Since we saved our data in <tt>Qt::EditRole</tt> we used <tt>edit</tt> as role name (a list of the default role names is [http://doc.qt.io/qt-5/qabstractitemmodel.html#roleNames In <tt>QAbstractItemModel</tt>'s documentation]) and then accesses directly all the properties of <tt>Person</tt> directly

Latest revision as of 20:33, 24 May 2021

The Problem

Sometimes you might want to use a custom class as data of a model that you'll then display in QML. While the process is pretty straightforward, it might be difficult to put together all the infos you need so we'll summarise the process using a small example.

Letting QML use your custom class

Say we have a pretty simple custom class, 3 members with getter and setter functions plus a const method that does a simple calculation

 
#include <QString>
#include <QDate>
class Person
{
public:
    Person(const QString& name ,const QString& surname,const QDate& dob)
        :m_birthDate(dob),m_name(name),m_surname(surname){}
    Person() = default;
    Person(const Person& other)=default;
    Person& operator=(const Person& other)=default;
    const QDate& birthDate() const { return m_birthDate; }
    void setBirthDate(const QDate& birthDate) { m_birthDate = birthDate; }
    const QString& name() const { return m_name; }
    void setName(const QString &name) { m_name = name; }
    const QString& surname() const { return m_surname; }
    void setSurname(const QString &surname) { m_surname = surname; }
    int age() const {
        if(m_birthDate.isNull())
            return -1;
        const QDate today = QDate::currentDate();
        return 
            (((12*(today.year()-m_birthDate.year()))+today.month()-m_birthDate.month())/12)
            - (today.month()==m_birthDate.month() && today.day()<m_birthDate.day()) ? 1:0
        ;
    }
private:
    QDate m_birthDate;
    QString m_name;
    QString m_surname;
};
  

The first thing we need to do is allow QML to interact with this class. To achieve this we'll make it a Q_GADGET. A Q_GADGET is a lighter version of Q_OBJECT: it doesn't allow you to use signals, slots and the parent/child ownership system but allows you to use Q_PROPERTY and Q_INVOKABLE.

 
#include <QString>
#include <QDate>
#include <QObject>
class Person
{
    Q_GADGET
    Q_PROPERTY(QString name READ name WRITE setName)
    Q_PROPERTY(QString surname READ surname WRITE setSurname)
    Q_PROPERTY(QDate birthDate READ birthDate WRITE setBirthDate)
    Q_PROPERTY(int age READ age)
public:
    Person(const QString& name ,const QString& surname,const QDate& dob)
        :m_birthDate(dob),m_name(name),m_surname(surname){}
    Person() = default;
    Person(const Person& other)=default;
    Person& operator=(const Person& other)=default;
    const QDate& birthDate() const { return m_birthDate; }
    void setBirthDate(const QDate& birthDate) { m_birthDate = birthDate; }
    const QString& name() const { return m_name; }
    void setName(const QString &name) { m_name = name; }
    const QString& surname() const { return m_surname; }
    void setSurname(const QString &surname) { m_surname = surname; }
    int age() const {
        if(m_birthDate.isNull())
            return -1;
        const QDate today = QDate::currentDate();
        return 
            (((12*(today.year()-m_birthDate.year()))+today.month()-m_birthDate.month())/12)
            - (today.month()==m_birthDate.month() && today.day()<m_birthDate.day()) ? 1:0
        ;
    }
private:
    QDate m_birthDate;
    QString m_name;
    QString m_surname;
};
  

Here we declared 4 properties of the object, 3 for the members and one for the age calculation.

Using your class in a C++ model

This part is straightforward, we'll build a QObject that fills a list model with dummy data and exposes it in a way QML can manage

 
#include <QStandardItemModel>
#include "person.h"
class People : public QObject{
    Q_OBJECT
    Q_PROPERTY(QAbstractItemModel* model READ model)
    Q_DISABLE_COPY(People)
public:
    explicit People(QObject* parent = Q_NULLPTR)
        :QObject(parent)
    {
        m_model=new QStandardItemModel(this);
        m_model->insertColumn(0);
        fillDummyData();
    }
    Q_SLOT void addPerson(const QString& name ,const QString& surname,const QDate& dob){
        if(name.isEmpty() && surname.isEmpty() && dob.isNull())
            return;
        const int newRow= m_model->rowCount();
        const Person newPerson(name,surname,dob);
        m_model->insertRow(newRow);
        m_model->setData(m_model->index(newRow,0),QVariant::fromValue(newPerson),Qt::EditRole);
    }
    QAbstractItemModel* model() const {return m_model;}
private:
    void fillDummyData(){
        addPerson(QStringLiteral("Albert"),QStringLiteral("Einstein"),QDate(1879,3,14));
        addPerson(QStringLiteral("Robert"),QStringLiteral("Oppenheimer"),QDate(1904,4,22));
        addPerson(QStringLiteral("Enrico"),QStringLiteral("Fermi"),QDate(1901,9,29));
    }
    QAbstractItemModel* m_model;
};
  

We expose the model through a property and we also added a slot to add a new person to the list. We used QStandardItemModel here just for convenience.

Passing to QML

The main function is very simple, the only change we made to QtCreator's default is creating the People object and passing it to the root context of QtQuick

 
#include <QGuiApplication>
#include <QQmlContext>
#include <QQmlApplicationEngine>
#include "people.h"
int main(int argc, char *argv[])
{
#if defined(Q_OS_WIN)
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
    People people;
    engine.rootContext()->setContextProperty("people", &people);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;
    return app.exec();
}
  

Using the data in QML

We'll display the data in a ListView

 
import QtQml 2.2
import QtQuick 2.7
import QtQuick.Window 2.2
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.3

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Custom Class in C++ Model and QML View")

    GridLayout {
        columns: 3
        anchors.fill: parent

        Text{
            text: "Name"
            Layout.row: 0
            Layout.column: 0
        }

        TextField  {
            id: inputName
            Layout.row: 1
            Layout.column: 0
            focus: true

        }

        Text{
            text: "Surname"
            Layout.row: 0
            Layout.column: 1
        }

        TextField  {
            id: inputSurname
            Layout.row: 1
            Layout.column: 1
        }

        Text{
            text: "Date of Birth"
            Layout.row: 0
            Layout.column: 2
        }

        TextField  {
            id: inputDob
            Layout.row: 1
            Layout.column: 2
            validator: RegExpValidator{regExp: /\d{4}-\d{2}-\d{2}/}
        }

        Button {
            text: "Add"
            Layout.row: 2
            Layout.column: 0
            Layout.columnSpan: 3
            onClicked: {
                people.addPerson(inputName.text, inputSurname.text, Date.fromLocaleString(Qt.locale(),inputDob.text,"yyyy-MM-dd"))
            }
        }

        ListView {
            width: 200; height: 250
            model: people.model
            delegate: Text {
                text: edit.name + " " + edit.surname +" "+ edit.birthDate.toLocaleString(Qt.locale(),"yyyy-MM-dd") + " ("+ edit.age +")"
            }
            Layout.row: 3
            Layout.column: 0
            Layout.columnSpan: 3
        }
    }
}
  

The first thing to notice is that we used the model property from People as the ListView's model. Now let's focus on the delegate, as you can see accessing our data is very easy we just use the roleName.propertyName syntax. Since we saved our data in Qt::EditRole we used edit as role name (a list of the default role names is In QAbstractItemModel's documentation) and then accesses directly all the properties of Person directly