PropertyBinder class in Qt

In QML we have this super-cool feature that allows us to bind the property value of one object as a function of the property value of another object. For instance

Item {
   width: anotherItem.width * 2
}

In the example above, the width of one item is twice the width of another item. If for any reason anotherItem.width changes, width is recomputed. This is a super-cool feature.

If we want a similar feature in C++ (with Qt ofcourse), we dont have a ready made class for that.

Imagine being able to do this

QSlider *slider = ...
QLabel *label = ....

new PropertyBinder(slider, "value", label, "text");

This will ensure that whenever the value of slider changes, the text of label is automatically updated.

Now, what if we wanted the value of text to be twice the value of slider. Imagine this..

QSlider *slider = ....
QLabel *label = ....

new PropertyBinder(slider, "value", label, "text", 
      [](const QVariant &v) { return v.toInt()*2; });

Wouldnt this be cool?

Below is implementation of the PropertyBinder class.

#ifndef PROPERTYBINDER_H
#define PROPERTYBINDER_H

#include <QObject>
#include <QPointer>
#include <QMetaObject>
#include <QMetaMethod>
#include <QMetaProperty>

#include <functional>

class PropertyBinder : public QObject
{
 Q_OBJECT

public:
 PropertyBinder(QObject *source, const QByteArray &sourceProp,
                 QObject *dest, const QByteArray &destProp)
   : QObject(source),
     m_source(source), m_sourceProperty(sourceProp),
     m_destination(dest), m_destinationProperty(destProp),
     m_useEvaluator(false)
     { this->hookToSourceProperty(); }

 PropertyBinder(QObject *source, const QByteArray &sourceProp,
                 QObject *dest, const QByteArray &destProp,
                 const std::function<QVariant (QVariant)> &evaluator)
    : QObject(source),
      m_source(source), m_sourceProperty(sourceProp),
      m_destination(dest), m_destinationProperty(destProp),
      m_useEvaluator(true), m_evaluator(evaluator)
      { this->hookToSourceProperty(); }

 QObject *source() const { return m_source; }
 QByteArray sourceProperty() const { return m_sourceProperty; }
 QObject *destination() const { return m_destination; }
 QByteArray destinationProperty() const { return m_destinationProperty; }

private:
 void hookToSourceProperty() {
   if(m_source.isNull() || m_destination.isNull())
     return;
   const QMetaObject *sourceMetaObject = m_source->metaObject();
   const int sourcePropertyIndex = sourceMetaObject->indexOfProperty( m_sourceProperty );
   if(sourcePropertyIndex < 0)
     return;
   const QMetaProperty sourceMetaProperty = sourceMetaObject->property(sourcePropertyIndex);
   if(!sourceMetaProperty.hasNotifySignal())
     return;
   const QByteArray sourceNotifySignal = QByteArray("2") + sourceMetaProperty.notifySignal().methodSignature();
   connect(m_source, sourceNotifySignal, this, SLOT(onSourcePropertyChanged()));
   onSourcePropertyChanged(); 
 }

 Q_SLOT void onSourcePropertyChanged() {
   if(m_source.isNull() || m_destination.isNull())
     return;
   const QVariant sourcePropertyValue = m_source->property(m_sourceProperty);
   const QVariant destinationPropertyValue = m_useEvaluator ?
   m_evaluator(sourcePropertyValue) : sourcePropertyValue;
   m_destination->setProperty(m_destinationProperty, destinationPropertyValue);
 }

private:
 QPointer<QObject> m_source;
 QByteArray m_sourceProperty;
 QPointer<QObject> m_destination;
 QByteArray m_destinationProperty;
 bool m_useEvaluator;
 std::function<QVariant (QVariant)> m_evaluator;
};

#endif // PROPERTYBINDER_H

Ofcourse, this will work only if properties have notify signal associated with them in the source object.


Posted

in

by