Skip to main content

Raymii.org Raymii.org Logo

Quis custodiet ipsos custodes?
Home | About | All pages | Cluster Status | RSS Feed

Qt Property Macro (Q_PROPERTY with 95% less code)

Published: 29-05-2024 22:00 | Author: Remy van Elst | Text only version of this article



qt logo

Adding properties to QObject based classes is cumbersome. Although the property system and Signals and Slots are great to use, especially with QML, it takes a lot of boilerplate code to add such properties to a class, at least 15 to 20 lines for each property. I've cobbled up a macro that saves you about 95% of lines when using a Q_PROPERTY. (22 lines to 1 line).

Recently I removed all Google Ads from this site due to their invasive tracking, as well as Google Analytics. Please, if you found this content useful, consider a small donation using any of the options below:

I'm developing an open source monitoring app called Leaf Node Monitoring, for windows, linux & android. Go check it out!

Consider sponsoring me on Github. It means the world to me if you show your appreciation and you'll help pay the server costs.

You can also sponsor me by getting a Digital Ocean VPS. With this referral link you'll get $200 credit for 60 days. Spend $25 after your credit expires and I'll get $25!

Adding a Q_PROPERTY

To add a property to a QObject based class, you have to add the following items to the class:

  • Q_PROPERTY statement
  • Declaration of getter and setter methods
  • Definition of getter and setter methods
  • Value changed signal definition
  • Member variable

Recent versions of Qt Creator have made this more efficient by auto-filling the Q_PROPERTY statement and via a right-click you can now go to Refactor then Add missing Q_PROPERTY members, but that is still more work and more typing than using a macro that does it all for you.

With this macro, QObject::connect still works as you would expect just as accessing the property from QML.

There also is the MEMBER Qt property type but that is still more boilerplate than this because you still need to write the entire line including notify signal and member variables.

QObject Property Macro

This is the macro I've defined:

/* Macro to define Q_PROPERTY backed by a regular value
 * QP_V = Q_Property, value (not reference) */
#define QP_V(Type, Name) \
private: \
    Q_PROPERTY(Type Name READ Name WRITE set##Name NOTIFY Name##Changed FINAL) \
    Type _##Name; \
public: \
    Type Name() const { return _##Name; } \
public Q_SLOTS: \
    void set##Name(Type value) { if (_##Name != value) { _##Name = value; Q_EMIT Name##Changed(value); } } \
Q_SIGNALS: \
    void Name##Changed(Type)
// end macro

Due to macro expansion combined with moc I'm using Q_SIGNALS, Q_SLOTS and Q_EMIT. The regular signals, slots and emit keywords do not work in this construction. These macro's can also be used if you want to use a third-party signals and slots system.

You can use it like this:

QP_V(property Type, property Name);

Example:

QP_V(bool, myBool);

The only thing I haven't got working is capitalization of function names. Convention is to use setMyThingChanged. With the macro, that becomes setmyThingChanged. You can capitalize you variable to fix that.

The macro is simple enough that you can easily update it for for example CONSTANT properties or read-only properties (without a WRITE method).

Usage example:

In my monitoring app there is an update check, which uses a Q_PROPERTY. That is now just one line:

QP_V(bool, newVersionAvailable);

Instead of all of these lines in the header file:

private:
    Q_PROPERTY(bool newVersionAvailable READ newVersionAvailable WRITE setNewVersionAvailable NOTIFY newVersionAvailableChanged FINAL)```

public:
  bool newVersionAvailable() const;

signals:
    void newVersionAvailableChanged(bool newVersionAvailable);

public slots:
  setNewVersionAvailable(bool newNewVersionAvailable);

private:
   bool _newVersionAvailable = false;    

And in the implementation (.cpp) file:

bool MyClass::newVersionAvailable() const
{
    return _newVersionAvailable;
}

void MyClass::setNewVersionAvailable(bool newNewVersionAvailable)
{
    if (_newVersionAvailable == newNewVersionAvailable)
        return;

    _newVersionAvailable = newNewVersionAvailable;
    emit newVersionAvailableChanged(_newVersionAvailable);
}

Saving all that boilerplate code makes it easier to read the source and figure out what actually happens and matters instead of hundreds and hundreds of lines of boilerplate.

Tags: blog , c++ , cmake , cpp , moc , property , q_property , qmake , qml , qt , qt5