12.3. Q_PROPERTY Macro - Describing QObject Properties

[ fromfile: reflection.xml id: qproperty ]

The property facility gives us a choice of ways to access data members:

You can specify read-only access to some properties, by leaving out a WRITE function. In addition, you can provide a NOTIFY signal to emit when the property is changed.

In Example 12.3, we have a customer class with a Qt property defined for each data member. The possible types for a property are those listed in QVariant::Type, plus user types added with Q_DECLARE_METATYPE (see Section 12.6). We have adopted the common practice of basing the name of each property that corresponds to a data member on the corresponding member name. For the member m_DataItem we named the corresponding property dataItem.

Example 12.3. src/properties/customer-props.h

[ . . . . ]
class Customer : public QObject {
    Q_OBJECT                                                        1

    /* Each property declaration has the following syntax:
            
    Q_PROPERTY( type name READ getFunction [WRITE setFunction]
    [RESET resetFunction] [NOTIFY notifySignal] [DESIGNABLE bool]
    [SCRIPTABLE bool] [STORED bool] )
    */

    Q_PROPERTY( QString id READ getId WRITE setId NOTIFY valueChanged);
    Q_PROPERTY( QString name READ getName WRITE setName 
                NOTIFY valueChanged);
    Q_PROPERTY( QString address READ getAddress WRITE setAddress 
                NOTIFY addressChanged);
    Q_PROPERTY( QString phone READ getPhone WRITE setPhone 
                NOTIFY phoneChanged);
    Q_PROPERTY( QDate dateEstablished READ getDateEstablished );    2
    Q_PROPERTY( CustomerType type READ getType WRITE setType 
                NOTIFY valueChanged);

  public:
    enum CustomerType
    { Corporate, Individual, Educational, Government };             3
    Q_ENUMS( CustomerType ) ;                                       4

    explicit Customer(const QString name = QString(),               5 
        QObject* parent = 0);                                       
                                                                  
    QString getId() const {
        return m_id;
    }
[ . . . . ]
    // Overloaded, so we can set the type 2 different ways:
    void setType(CustomerType newType);
    void setType(QString newType);
signals:
    void addressChanged(QString newAddress);
    void phoneChanged(QString newPhone);
    void typeChanged(CustomerType type);
    void valueChanged(QString propertyName,
        QVariant newValue, QVariant oldValue = QVariant());
private:
    QString m_id, m_name, m_address, m_phone;
    QDate m_date;
    CustomerType m_type;
};
[ . . . . ]

1

Macro required for moc to preprocess class.

2

Read-only property

3

The enum type definition must be in the same class definition as the Q_ENUMS macro.

4

Special macro to generate string-to-enum conversion functions; must be in same class.

5

Declared explicit because we do not want accidental conversions from QString to Customer.

<include src="src/properties/customer-props.h" href="src/properties/customer-props.h" id="customerpropsh" mode="cpp"/>


Notice the enum CustomerType defined in the public section of the class Customer. Just above that definition, the Q_ENUMS macro tells moc to generate some functions for this property in the QMetaProperty to aid in string conversions for enum values.

The setters and getters, defined in Example 12.4, are implemented in the usual way.

Example 12.4. src/properties/customer-props.cpp

[ . . . . ]
Customer::Customer(const QString name, QObject* parent)
:QObject(parent) {
    setObjectName(name);
}

void Customer::setId(const QString &newId) {
    if (newId != m_id) {  
        QString oldId = m_id;
        m_id = newId;     
        emit valueChanged("id", newId, oldId);
    }
}
[ . . . . ]
void Customer::setType(CustomerType theType) {
    if (m_type != theType) {
        CustomerType oldType = m_type;
        m_type = theType;
        emit valueChanged("type", theType, oldType);
    }
}

/* Method for setting enum values from Strings. */
void Customer::setType(QString newType) {           1

    static const QMetaObject* meta = metaObject();  2
    static int propindex = meta->indexOfProperty("type");
    static const QMetaProperty mp = meta->property(propindex);

    QMetaEnum menum = mp.enumerator();              3
    const char* ntyp = newType.toAscii().data();
    CustomerType theType =
                   static_cast<CustomerType>(menum.keyToValue(ntyp));
    
    if (theType != m_type) {                        4
        CustomerType oldType = m_type;
        m_type = theType;
        emit valueChanged("type", theType, oldType);
    }
}

QString Customer::getTypeString() const {
    return property("type").toString();
}
[ . . . . ]

1

Overloaded version that accepts a string as an argument. Sets value to -1 if unknown.

2

Because they are static locals, the initializations happen only once.

3

This code gets executed each time.

4

Always check if valueChanged signal is needed.

<include src="src/properties/customer-props.cpp" href="src/properties/customer-props.cpp" id="customerpropscpp" mode="cpp"/>


The implementation of the overloaded function setType(QString) takes advantage of QMetaProperty's Q_ENUMS macro to convert the QString to the proper enumerated value. To obtain the correct QMetaProperty object for an enum, you first get the QMetaObject and call indexOfProperty() and property() to find it. QMetaProperty has a function called enumerator() that returns an object we can use to convert strings to enums. If the given QString argument does not match one of the enumerators, the keyToValue() function returns –1.



[90] Of course, this depends on how expensive creating the objects are and how often the function is called.