A way to avoid boilerplate code for defining QObject properties in C++ (to be accessible from QML)?

638 views Asked by At

The Qt docs contain this example:

class Message : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged)
public:
    void setAuthor(const QString &a) {
        if (a != m_author) {
            m_author = a;
            emit authorChanged();
        }
    }
    QString author() const {
        return m_author;
    }
signals:
    void authorChanged();
private:
    QString m_author;
};

Is there a way to avoid writing all this boilerplate code just to define a QML-interoperable property? For example, since this code is calling the Q_PROPERTY macro anyway, can't this macro do the dirty work for me and define all the rest automatically?

Note: I've found a nice-looking set of macros here but it's not an official part of Qt, so I'm not sure if it's free of gotchas.

2

There are 2 answers

0
evilruff On

There is no standard solution for this, but then I looked on this issue while ago I found this, QmlTricks which I think is really nice implementation.

MEMBER is a way to go, but sometimes you might need more control of what's happening inside setter/getter methods, so to be honest I don't use it often.

I made my own subset with some additional macros like QObject's support for properties, custom setter/getter etc.

#define QOBJECT_CONSTANT_PROPERTY(typeName, propertyName, getterName, setterName) \
public: \
    Q_PROPERTY (typeName * propertyName READ getterName CONSTANT) \
protected: \
QPointer<typeName> m_##getterName; \
public: \
    typeName * getterName () const { \
    return m_##getterName; \
    } \
public Q_SLOTS: \
    bool setterName (typeName * newVal) { \
        bool ret = false; \
        if ((ret = m_##getterName != newVal)) { \
        m_##getterName = newVal; \
        } \
        return ret; \
    } \
public:


#define QOBJECT_WRITABLE_PROPERTY(typeName, propertyName, getterName, setterName, signalName) \
public: \
Q_PROPERTY(typeName * propertyName READ getterName WRITE setterName NOTIFY signalName) \
protected: \
QPointer<typeName>  m_##getterName; \
public: \
typeName * getterName() const { \
return m_##getterName; \
} \
public Q_SLOTS: \
bool setterName(typeName * newVal) { \
bool ret = false; \
if ((ret = (m_##getterName != newVal))) { \
m_##getterName = newVal; \
    emit signalName(#propertyName); \
} \
return ret; \
} \
Q_SIGNALS: \
void signalName(const char *); \
public:

#define QOBJECT_READONLY_PROPERTY(typeName, propertyName, getterName, setterName, signalName) \
public: \
    Q_PROPERTY (typeName * propertyName READ getterName NOTIFY signalName) \
protected: \
QPointer<typeName> m_##getterName; \
public: \
    typeName * getterName () const { \
    return m_##getterName; \
    } \
public Q_SLOTS: \
    bool setterName (typeName * newVal) { \
        bool ret = false; \
        if ((ret = (m_##getterName != newVal))) { \
        m_##getterName = newVal; \
        emit signalName (#propertyName); \
        } \
        return ret; \
    } \
 Q_SIGNALS: \
    void signalName (const char *); \
public:

#define WRITABLE_PROPERTY(typeName, propertyName, getterName, setterName, signalName) \
public: \
    Q_PROPERTY (typeName propertyName READ getterName WRITE setterName NOTIFY signalName) \
protected: \
typeName m_##getterName; \
public: \
    typeName getterName () const { \
    return m_##getterName; \
    } \
public Q_SLOTS: \
bool setterName (const typeName & newVal) { \
        bool ret = false; \
        if ((ret = (m_##getterName != const_cast<typeName&>(newVal)))) { \
        m_##getterName = newVal; \
        emit signalName (#propertyName); \
        } \
        return ret; \
    } \
Q_SIGNALS: \
void signalName ( const char * ); \
public:


#define WRITABLE_PROPERTY_USER_GETTER(typeName, propertyName, getterName, setterName, signalName) \
public: \
    Q_PROPERTY(typeName propertyName READ getterName WRITE setterName NOTIFY signalName) \
protected: \
typeName m_##getterName; \
    public Q_SLOTS: \
bool setterName(const typeName & newVal) { \
bool ret = false; \
if ((ret = (m_##getterName != const_cast<typeName&>(newVal)))) { \
    m_##getterName = newVal; \
    emit signalName(#propertyName); \
} \
    return ret; \
} \
Q_SIGNALS: \
void signalName(const char *); \
public:


#define WRITABLE_PROPERTY_USER_SETTER(typeName, propertyName, getterName, setterName, signalName) \
public: \
    Q_PROPERTY (typeName propertyName READ getterName WRITE setterName NOTIFY signalName) \
protected: \
typeName m_##getterName; \
public: \
    typeName getterName () const { \
    return m_##getterName; \
    } \
Q_SIGNALS: \
void signalName ( const char * ); \
public:




#define READONLY_PROPERTY(typeName, propertyName, getterName, setterName, signalName) \
public: \
    Q_PROPERTY (typeName propertyName READ getterName NOTIFY signalName) \
protected: \
typeName m_##getterName; \
public: \
    typeName getterName () const { \
    return m_##getterName; \
    } \
public Q_SLOTS: \
    bool setterName (const typeName & newVal) { \
        bool ret = false; \
        if ((ret = (m_##getterName != const_cast<typeName&>(newVal)))) { \
        m_##getterName = newVal; \
        emit signalName (#propertyName); \
        } \
        return ret; \
    } \
 Q_SIGNALS: \
    void signalName (const char *); \
public:


#define CONSTANT_PROPERTY(typeName, propertyName, getterName, setterName) \
public: \
    Q_PROPERTY (typeName propertyName READ getterName CONSTANT) \
protected: \
typeName m_##getterName; \
public: \
    typeName getterName () const { \
    return m_##getterName; \
    } \
public Q_SLOTS: \
    bool setterName (const typeName & newVal) { \
        bool ret = false; \
        if ((ret = m_##getterName != newVal)) { \
        m_##getterName = const_cast<typeName&>(newVal); \
        } \
        return ret; \
    } \
public:

#define LIST_PROPERTY(CLASS, NAME, TYPE) \
public: \
    static int NAME##_count (QQmlListProperty<TYPE> * prop) { \
        CLASS * instance = qobject_cast<CLASS *> (prop->object); \
        return (instance != NULL ? instance->m_##NAME.count () : 0); \
    } \
    static void NAME##_clear (QQmlListProperty<TYPE> * prop) { \
        CLASS * instance = qobject_cast<CLASS *> (prop->object); \
        if (instance != NULL) { \
            instance->m_##NAME.clear (); \
        } \
    } \
    static void NAME##_append (QQmlListProperty<TYPE> * prop, TYPE * obj) { \
        CLASS * instance = qobject_cast<CLASS *> (prop->object); \
        if (instance != NULL && obj != NULL) { \
            instance->m_##NAME.append (obj); \
        } \
    } \
    static TYPE * NAME##_at (QQmlListProperty<TYPE> * prop, int idx) { \
        CLASS * instance = qobject_cast<CLASS *> (prop->object); \
        return (instance != NULL ? instance->m_##NAME.at (idx) : NULL); \
    } \
    QList<TYPE *> get_##NAME##s (void) const { \
        return m_##NAME; \
    } \
private: \
    QList<TYPE *> m_##NAME;
2
dtech On

You can avoid the implementation of accessor functions if you use MEMBER properties. Then you only need the member and the optional notify signal.

class Message : public QObject {
    Q_OBJECT
    Q_PROPERTY(QString author MEMBER m_author NOTIFY authorChanged)  
    QString m_author;
public:
    // ...
signals:
    void authorChanged();
};

The macro approach should work fine, however there are two problems with the one you have linked:

  • getters are not const
  • the notify signal should not emit the value

So using MEMBER you can simplify it further:

#define QPROP(type, name) \
  private: \
  Q_PROPERTY(type name MEMBER m_ ## name NOTIFY name ## Changed ) \
  type m_ ## name; \
  public: \
  Q_SIGNAL void name ## Changed();

And then just:

class Message : public QObject {
    Q_OBJECT
    QPROP(QString, author)
};