Macros to Simplify Redundant Q_PROPERTY() Hell

When you need to expose C++ object for accessing in QML, you may have to write a lot of annoying and trivial Q_PROPERTY()s, getters, setter slots, signals, and private member variables. They will makes your class a mess (any mostly are duplicated). For example, a QString myFoo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyObjectExposedToQml : public QObject
{
Q_OBJECT
Q_PROPERTY(QString myFoo READ myFoo WRITE setMyFoo NOTIFY myFooChanged)
public:
QString myFoo() const { return m_myFoo ; }
public slots:
void setMyFoo(QString myFoo) {
if (m_myFoo == myFoo) { return; }
m_myFoo = myFoo;
emit myFooChanged(myFoo);
}
signals:
void myFooChanged(QString myFoo);
private:
QString m_myFoo;
}

Though only one property here but has been nearly create a getter/setter/signal/variable hell. So make some macros to get rid of these shit:

Important: in macro definition, signals: and slots: must be replaced with Q_SIGNALS: and Q_SLOTS. Otherwise compilation will fail.
(this problem cost me hours to find out…)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#define RW_PROP(T, R, W)
// Read + Write in C++
// Read + Write in QML
public:\
T R() const { return m_##R ; } \
public Q_SLOTS: \
void W(T R) {\
if (m_##R == R) { return; } \
m_##R = R;\
emit R##Changed(R);} \
Q_SIGNALS:\
void R##Changed(T R); \
private:\
Q_PROPERTY(T R READ R WRITE W NOTIFY R##Changed);\
T m_##R;

// Read + Write in C++ (Same as above)
// Read-only in QML (Because `WRITE setterFunction` is not specified in Q_PROPERTY)
#define RO_PROP(T, R, W) \
public:\
T R() const { return m_##R ; } \
public Q_SLOTS: \
void W(T R) {\
if (m_##R == R) { return; } \
m_##R = R;\
emit R##Changed(R);} \
Q_SIGNALS:\
void R##Changed(T R); \
private:\
Q_PROPERTY(T R READ R NOTIFY R##Changed);\
T m_##R;

For those who have ever used Angular / Vue and their data-binding


If you have ever used Angular / Vue, you should be familiar with its easy-to-use data-binding; framework will aggressively watch if any value in model is modified, then update view automatically. But QML is not that smart.

This is how QML’s data-binding syntax looks like:
1
2
3
Button {
text: my_object_exposed_to_qml.myFoo
}


Equivalent to JS’s

1
2
3
4
5
6
Button {
Component.onCompleted: {
// Qt.binding() is required, otherwise Button.text won't be updated when myFooChanged() signal is emitted.
text = Qt.binding(function(){ return my_object_exposed_to_qml.myFoo })
}
}


QML’s property binding actually relys on Qt’s signal, (e.g. NOTIFY myFooChanged above), QML itself will NOT aggressively watch the modification from getter function. The specified signal (NOTIFY signalFunctionName) must be invoked (e.g. emit myFooChanged), then QML side can know that the value of property is changed.

Therefore, in C++, use setter (or emit signal manually) instead of assigning the member variable directly, otherwise QML won’t be notified when the value is changed.

Usage

1
2
3
4
5
6
7
8
9
10
class MyObjectExposedToQml : public QObject
{
Q_OBJECT
public:
/* ... Implement some truly necessary methods here ... */
private:
RW_PROP(QString, myFoo, setMyFoo)
RW_PROP(int, myBar, setMyBar)
RW_PROP(QUrl, hello, setHello)
}

Oh yeah!

Surely it’s not a good practice to place function definition in header. But if you need, you can think how to do that. (This is against the topic of this article.)