Synthesize Qt properties (write properties with less code)

Tags: , , ,

There are some utils that I use in most of my Qt projects. I’m a bit fed up of copy-pasting those every time I need something, so I created a project containing tools that are of frequent use when I write Qt apps, regardless of the type of app.

One of the tools I use the most are the shortcuts to define Qt properties. Writing stored Qt properties is extremely frequent, in particular when dealing with QML. This is an example of how to add two properties to a class:

class Fraction : public QObject<br>
{
    Q_OBJECT
    Q_PROPERTY(double numerator READ numerator WRITE setNumerator NOTIFY numeratorChanged)
    Q_PROPERTY(double denominator READ denominator WRITE setDenominator NOTIFY denominatorChanged)
public:
    Fraction(QObject* parent = nullptr) : QObject(parent) {}<br>
    double numerator() const {
        return m_numerator;
    }
    double denominator() const {
        return m_denominator;
    }
public slots:
    void setNumerator(double numerator) {
        if (m_numerator == numerator)
            return;
        m_numerator = numerator;
        emit numeratorChanged(numerator);
    }
    void setDenominator(double denominator) {
        if (m_denominator == denominator)
            return;
         m_denominator = denominator;
         emit denominatorChanged(denominator);
    }
signals:
    void numeratorChanged(double numerator);
    void denominatorChanged(double denominator);
private:
    double m_numerator;
    double m_denominator;
};

This code must be repeated way too many times. I have projects with thousands of properties, with the same pattern.

So I added a few macros to my project here. You can use macros like:

L_RW_PROP
L_RO_PROP

to synthesize props, with getter, setter and notifications. Each macro is overloaded: with 3 params, you do not have initialization, adding a 4th param, also initializes the variable to the provided value. Also, I frequently need QObject that are just containers of properties, to be used in QML. I added these two macros to spare some lines:

L_BEGIN_CLASS(<class_name>)
L_END_CLASS

A class like the one above, can simply be written like:

L_BEGIN_CLASS(Fraction)
L_RW_PROP(double, numerator, 0)
L_RW_PROP(double, denominator, 0)
L_END_CLASS

There are other variants to those two macros. You can have references, you can customize the setter name etc… For a complete list, refer to the readme.

This is an example of how I use this in a real project, for example:

L_BEGIN_CLASS(FPQueryResultItem)
L_RW_PROP_AS(QString, ID)
L_RW_PROP_AS(QString, UID)
L_RW_PROP_AS(QDateTime, TakenAt)
L_RW_PROP_AS(int, Year, -1)
L_RW_PROP_AS(int, Month, -1)
L_RW_PROP_AS(int, Day, -1)
L_RW_PROP_AS(QString, FileName)
L_RW_PROP_AS(QString, FileRoot)
L_RW_PROP_AS(QString, Name)
L_RW_PROP_AS(QString, Path)
L_RW_PROP_AS(QString, Hash)
L_RW_PROP_AS(QString, Type)
public:
    Q_INVOKABLE bool isSidecar() { return m_FileRoot == QStringLiteral("sidecar"); }
    Q_INVOKABLE bool isVideo() { return m_Type == QStringLiteral("video"); }
    Q_INVOKABLE bool isImage() { return m_Type == QStringLiteral("image"); }
L_END_CLASS

This class includes 12 Q_PROPERTY’s, resulting from the deserialization of JSON objects. The methods are unrelated to the definition, those use prop values to provide more functionalities.

Objects like `QString`’s and `QDateTime`’s do not need to be initialized, as their default constructor is automatically called when the constructor of `FPQueryResultItem` is called. int‘s, instead, would be undefined without providing the initial value. This is something you frequently do not want.

I’m sure there are other similar approaches out there, but if you need it, you can simply add this repo as a submodule like I do: https://github.com/carlonluca/lqtutils.

Bye! 😉

Leave a Reply

Your email address will not be published. Required fields are marked *