12.5.  Dynamic Properties

[ fromfile: reflection.xml id: dynamicproperties ]

It is possible to load and store properties in a QObject without having to define them on the class with Q_PROPERTY.

Up to this point we have been dealing exclusively with properties that are defined with the Q_PROPERTY macro. These properties are known to the QMetaObject of that class, and have a QMetaProperty defined. All objects of the same class share the same metaObject and thus have the same set of meta properties.

Dynamic properties, on the other hand, are acquired at runtime and are specific to the object that acquired them. In other words, two objects of the same class have the same meta property list, but they can have different lists of dynamic properties. In Example 12.8, we define a class with a single Q_PROPERTY, which we name someString.

Example 12.8. src/properties/dynamic/dynoprops.h

[ . . . . ]
class DynoProps : public QObject {
    Q_OBJECT
    Q_PROPERTY(QString someString READ someString WRITE setSomeString);
 public:
    friend QDataStream& operator<<(QDataStream& os, const DynoProps& dp);
    friend QDataStream& operator>>(QDataStream& is, DynoProps& dp);
    QString someString() { return m_someString; }
    void setSomeString(QString ss) { m_someString = ss; }
    QString propsInventory();
 private:
    QString m_someString;
};
[ . . . . ]

<include src="src/properties/dynamic/dynoprops.h" href="src/properties/dynamic/dynoprops.h" id="dynopropsh" mode="cpp"/>


In Example 12.9, the implementation of propsInventory() shows a way to display fixed and dynamic properties. The list of the fixed properties comes from the QMetaObject. You can access property values using QMetaProperty::read() or QObject::property(). The propertyCount() function sets a limit for iteration through the QMetaProperty list.

The dynamic properties are not known by the QMetaObject. Instead, you must use QObject methods. You can iterate through the QList returned by QObject::dynamicPropertyNames() for the list of names, and use QObject::property() to obtain values.

Example 12.9. src/properties/dynamic/dynoprops.cpp

[ . . . . ]

QString DynoProps::propsInventory() {
   static const QMetaObject* meta = metaObject();
   QStringList res;
   res << "Fixed Properties:";
   QString propData;
   for(int i = 0; i < meta->propertyCount(); ++i) {
      res << QString("%1\t%2").arg(QString(meta->property(i).name()))
              .arg(meta->property(i).read(this).toString());  1
   }
   res << "Dynamic Properties:";  
   foreach(QByteArray dpname, dynamicPropertyNames()) {
      res << QString("%1\t%2").arg(QString(dpname))
              .arg(property(dpname).toString());
   }
   return res.join("\n");
}
 

1

We could also use property(propName) here.

<include segid="inventory" mode="cpp" href="src/properties/dynamic/dynoprops.cpp" id="dynopropscpp" src="src/properties/dynamic/dynoprops.cpp"/>


Aside from the slight awkwardness of accessing them, dynamic properties can be used in much the same way as fixed properties; for example, they can be serialized. The implementations of the two serialization operators are shown in Example 12.10 We employ a similar technique for serialization that we used for the propsInventory() function.

Example 12.10. src/properties/dynamic/dynoprops.cpp

[ . . . . ]
 QDataStream& operator<< (QDataStream& os, const DynoProps& dp) {
   static const QMetaObject* meta = dp.metaObject();
   for(int i = 0; i < meta->propertyCount(); ++i) {
        const char* name = meta->property(i).name();
        os << QString::fromLocal8Bit(name)              1
           << dp.property(name);
   }
   qint32 N(dp.dynamicPropertyNames().count());         2
   os << N;
   foreach(QByteArray propname, dp.dynamicPropertyNames()) {
      os << QString::fromLocal8Bit(propname) << dp.property(propname);
   }
   return os;
 }
 
 QDataStream& operator>> (QDataStream& is, DynoProps& dp) {
   static const QMetaObject* meta = dp.metaObject();
   QString propname;
   QVariant propqv;
   int propcount(meta->propertyCount());
   for(int i = 0; i < propcount; ++i) {
      is >> propname;
      is >> propqv;
      dp.setProperty(propname.toLocal8Bit(), propqv);   3
   }
   qint32 dpcount;
   is >> dpcount;
   for(int i = 0; i < dpcount; ++i) {
      is >> propname;
      is >> propqv;
      dp.setProperty(propname.toLocal8Bit(), propqv);
   }
   return is;
 }

1

To serialize a char* as a QString

2

To serialize an int

3

De-serialize char* using reverse QString conversion

<include segid="serial" mode="cpp" href="src/properties/dynamic/dynoprops.cpp" id="dynoserial" src="src/properties/dynamic/dynoprops.cpp"/>


Client code to demonstrate the use of dynamic properties is shown in Example 12.11.

Example 12.11. src/properties/dynamic/dynoprops-client.cpp

#include <QtCore>
#include "dynoprops.h"

int main() {
   QTextStream cout(stdout);
   DynoProps d1, d2;
   d1.setObjectName("d1");
   d2.setObjectName("d2");
   d1.setSomeString("Washington");
   d1.setProperty("AcquiredProp", "StringValue");
   d2.setProperty("intProp", 42);
   d2.setProperty("realProp", 3.14159);
   d2.setProperty("dateProp", QDate(2012, 01, 04));
   cout << d1.propsInventory() << endl;
   cout << d2.propsInventory() << endl;
   cout << "\nNow we save both objects to a file, close the file,\n"
           "reopen the file, read the data from the file, and use it\n"
           "to create new DynoProps objects.\n" << endl;
   QFile file("file.dat");
   file.open(QIODevice::WriteOnly);
   QDataStream out(&file);
   out << d1 << d2;
   file.close();
   DynoProps nd1, nd2;
   file.open(QIODevice::ReadOnly);
   QDataStream in(&file);
   in >> nd1 >> nd2;
   file.close();
   cout << "Here are the property inventories for the new objects.\n";
   cout << nd1.propsInventory() << endl;
   cout << nd2.propsInventory() << endl;   
}
                                            

<include src="src/properties/dynamic/dynoprops-client.cpp" href="src/properties/dynamic/dynoprops-client.cpp" id="dynoclient" mode="cpp"/>


Example 12.12 shows the output of the program.

Example 12.12. src/properties/dynamic/output.txt

Fixed Properties:
objectName      d1
someString      Washington
Dynamic Properties:
AcquiredProp    StringValue
Fixed Properties:
objectName      d2
someString
Dynamic Properties:
intProp 42
realProp        3.14159
dateProp        2012-01-04

Now we save both objects to a file, close the file,
reopen the file, read the data from the file, and use it
to create new DynoProps objects.

Here are the property inventories for the new objects.
Fixed Properties:
objectName      d1
someString      Washington
Dynamic Properties:
AcquiredProp    StringValue
Fixed Properties:
objectName      d2
someString
Dynamic Properties:
intProp 42
realProp        3.14159
dateProp        2012-01-04

<include src="src/properties/dynamic/output.txt" href="src/properties/dynamic/output.txt" id="dynoutput" mode="text"/>