Location>code7788 >text

QT Binable Property QObjectBindableProperty QObjectComputedProperty, a method to simplify signal and slot (SIGNAL, SLOT) mechanism

Popularity:528 ℃/2025-04-12 17:35:28

The bindingable properties provided by QT means that these properties can be bound to other values ​​or expressions (usually C++ lambda expressions). If the property is bound by an expression, the property will be automatically updated with the expression. Bindable properties are implemented by the QProperty class and the QObjectBindableProperty class, both inherited from the QPropertyData class. The QProperty class contains data objects and pointers to the management data structure (QPropertyBindingData); the QObjectBindableProperty class only contains data objects and uses encapsulated QObject to store pointers to the management data structure. In other words, QProperty does not rely on QT's metaObject system, and QObjectBindableProperty needs to be used with QObject.

Why use bindable attributes

Attribute binding is one of the core attributes of QML. It allows specifying the relationships of different objects to be, and automatically updates the property values ​​when the object they depend on. Bindable attributes can be used not only in QML code, but in C++ code. Using bindable properties can simplify programming, thus omitting code that updates properties through tracking, response (signal, slot mechanism). Examples of simplified programming:/qt-6/

Example of binding properties in C++ code

The binding expression calculates the value of the bound property by reading the values ​​of other QProperty.When any attributes that the binding expression depends on change, the binding expression will be recalculated and the result will be used for the corresponding binding attribute.

QProperty<QString> firstname("John");
QProperty<QString> lastname("Smith");
QProperty<int> age(41);

QProperty<QString> fullname;
([&]() { return () + " " + () + " age: " + QString::number(()); });

qDebug() << (); // Prints "John Smith age: 41"

firstname = "Emma"; // Triggers binding reevaluation

qDebug() << (); // Prints the new value "Emma Smith age: 41"

// Birthday is coming up
(() + 1); // Triggers re-evaluation

qDebug() << (); // Prints "Emma Smith age: 42"

In the above example, when firstname changes, the binding expression recalculates the value of fullname. Therefore, when the last qDebug() statement accesses the fullname property, the latest value is returned.

Since the binding expression is a C++ method, it can do anything like a normal C++ method (for example, calling other methods).If the QProperty variable is used in the called method, the variable will automatically establish a dependency with the bound properties.

Any type of attribute can be used in a binding expression. In the above example, age is type int and converted to string type, but it is still dependent and traced by fullname.

Getters and Setters that can bind attributes

When using QProperty or QObjectBindableProperty in a class to declare binding properties, it is important to note when building Getters and Setters.

getters

To ensure the correct operation of the automatic dependency tracking system, the value needs to be read from the underlying property object in the getter. also,This property must not be written in getter. Design patterns that recalculate or update anything cannot be used in getters.Therefore, for binding properties,Recommend only the easiest getters

setters

To ensure the correct operation of the automatic dependency tracking system,In the setter, the value needs to be written to the underlying property object regardless of whether the value changes. Any other code in the setter is wrong. Any update operation performed with a new value should be considered a bug, because these codes will not be executed when the binding attribute is changed through the binding.Therefore, for binding properties,It is recommended to use only the easiest setters.

Virtual Setter and Virtual Getter

Setters and getters for binding properties should usually be minimal and only the properties are set; therefore,It is generally not suitable to set such setters and getters to virtual.This makes no sense to derived classes.

However, some Qt classes may have properties of virtual setters. Rewriting setters requires special care when inheriting such Qt classes. In any case, the basic implementation must be called for the binding to work properly. The method is as follows:

void DerivedClass::setValue(int val)
{
    // do something
    BaseClass::setValue(val);
    // probably do something else
}

All rules and suggestions for writing bindable properties also apply here. After calling the base class implementation, all observers will be notified about property changes. Therefore, before calling the base class implementation, it is necessary to ensure that the class reaches a stable state (that is, all the properties that need to be modified have been modified).

There are very few cases where virtual getter or setter is required. Declaring that virtual getter or setter base class should indicate the requirements for rewriting.

Suggestions for writing bindable properties

When the bindingable property changes, the property notifies each property that depends on the property. This triggers the property to change the handler, and any type of code may be executed when the handler is triggered. Therefore all codes that write binding properties must be carefully reviewed.

  1. The intermediate values ​​during calculation cannot be written to bindable properties
    Bindable attributes cannot be used as variables in algorithms. Each value written is communicated to the dependent attribute. For example, in the following code, other properties that depend on myProperty will be first told to change to 42 and then told to change to maxValue.
myProperty = somecomputation(); // returning, say, 42
if (() > maxValue)
    myProperty = maxValue;

The calculation should be performed using separate variables. The correct code is as follows:

int newValue = someComputation();
if (newValue > maxValue)
    newValue = maxValue;
myProperty = newValue; // only write to the property once
  1. Cannot write binding properties when the class is in a transition state

When a bindingable property is a member of a class, each write to the property may expose the current state to the outside. Therefore, when the class does not reach a stable state, binding-able attributes must not be written in the transition state of the class.
For example, in a class representing a circle, the members radius and area should be consistent, and the setter code is as follows (where radius is a bindable property):

void setRadius(double newValue)
{
    radius = newValue; // this might trigger change handlers
    area = M_PI * radius * radius;
    emit radiusChanged();
}

When the triggered handler uses the circle, radius is the latest value, but area has not been updated yet.

Rules for using attribute binding

Any C++ expression that can derive the correct type can be used as a binding expression and provided to the setBinding() method. However, to build the correct binding, some rules must be followed.

  1. Make sure that all attributes used in the binding expression are boundable attributes
    Dependency tracing is only applicable to bindable properties. When using non-binding properties in a binding expression, changes to these properties do not trigger updates to the bound properties. There will be no warning or error at compile time or runtime. The bound properties are updated only if the bindingable properties used in the binding expression change. If you can ensure that every change to the unbound property item can trigger the markDirty method of the bound property, you can use the unbound property in the binding.

  2. Make sure the life cycle of the object in the bound expression is long enough
    During the lifetime of an object, property binding may be recalculated multiple times. It is necessary to ensure that all objects used in the binding expression have a longer life cycle than the binding itself, otherwise it may result in runtime errors or unpredictable behavior.

  3. The binding attribute system is not thread-safe
    On one thread, the properties used in the binding expression cannot be read or modified by any other thread. Objects of QObject derived classes with bound properties must not be moved to other threads. Additionally, if the properties of the QObject derived class are used in the binding expression, the object must not move it to another thread. Neither the binding of properties in the same object or the binding of properties in another object is thread-safe.

  4. Avoid a dead cycle
    A bound expression should not read data from a bound property (i.e. the attribute assigned after the expression is calculated). Otherwise, a dead cycle will occur.

  5. **Binding expressions must not be written to their bound properties.

  6. ** The co_await keyword must not be used
    The functions used as binding and all code called within the binding shall not use co_await.Doing so may confuse the property system's tracking of dependencies.

How to track binding attributes

The above discussion is to bind attributes through setBinding(). Sometimes, the relationship between attributes cannot be expressed by binding. When dealing with property value changes, another method is required if instead of simply assigning the value to another property, it is passed to other parts of the application for further processing (for example, writing data to a network socket or printing debug output). QProperty provides two tracking mechanisms.

  1. Use onValueChanged() to register a callback function to handle property changes;
  2. Use subscribe() to register a callback function, which is different from onValueChanged() to process the current value of the property (that is, the callback function will be executed immediately when subscribe() is called).
    template<typename Functor>
    QPropertyChangeHandler<Functor> onValueChanged(Functor f)
    {
        static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters");
        return QPropertyChangeHandler<Functor>(*this, f);
    }

    template<typename Functor>
    QPropertyChangeHandler<Functor> subscribe(Functor f)
    {
        static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters");
        f();
        return onValueChanged(f);
    }

    template<typename Functor>
    QPropertyNotifier addNotifier(Functor f)
    {
        static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters");
        return QPropertyNotifier(*this, f);
    }

Interact with Q_PROPERTYs

If BINDABLE is specified in the Q_PROPERTY definition, this property can be bound and used in the binding expression. This property needs to be implemented through the QProperty, QObjectBindableProperty or QObjectComputedProperty definition properties. Examples of use are as follows:

#include <QObject>
#include <QProperty>
#include <QDebug>

class Foo : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int myVal READ myVal WRITE setMyVal BINDABLE bindableMyVal)
public:
    int myVal() { return (); }
    void setMyVal(int newvalue) { myValMember = newvalue; }
    QBindable<int> bindableMyVal() { return &myValMember; }
signals:
    void myValChanged();

private:
    Q_OBJECT_BINDABLE_PROPERTY(Foo, int, myValMember, &Foo::myValChanged);
};

int main()
{
    bool debugout(true); // enable debug log
    Foo myfoo;
    QProperty<int> prop(42);
    QObject::connect(&myfoo, &Foo::myValChanged, [&]() {
        if (debugout)
            qDebug() << ();
    });
    ().setBinding([&]() { return (); }); // prints "42"

    prop = 5; // prints "5"
    debugout = false;
    prop = 6; // prints nothing
    debugout = true;
    prop = 7; // prints "7"
}

#include ""

Q_PROPERTYs definition ifBINDABLE is not specified, but NOTIFY signal is specified, and can also be bound and used in bound expressions. At this point, the property must be wrapped in QBindable using the QBindable(QObject *obj, const char *property) constructor. You can then bind the property using QBindable::setBinding() or use it via QBindable::value() in the binding expression. If BINDABLE is not specified in the Q_PROPERTY definition, to start the dependency tracking function of the property, QBindable::value() must be used in the binding expression, and the property's READ function (or MEMBER) cannot be used. Examples are as follows:

#include <QObject>
 #include <QBindable>
 #include <QProperty>
 #include <QDebug>

 class Foo : public QObject
 {
     Q_OBJECT
     Q_PROPERTY(int myVal READ myVal WRITE setMyVal NOTIFY myValChanged CONSTANT)
 public:
     explicit Foo():m_myVal(5){}
     int myVal() const { return m_myVal; }
     void setMyVal(int newvalue) {
         if(m_myVal == newvalue) return;
         m_myVal = newvalue;
         emit myValChanged(newvalue);
     }
 signals:
     void myValChanged(int newVal);

 private:
     int m_myVal;
 };

 int main()
 {

     Foo myfoo;
     QBindable<int> obj(&myfoo, "myVal");
     QProperty<int> prop([&](){return ();});
	 // The return value of onValueChanged must be saved, otherwise the callback will be invalid
     auto change = ([&](){qDebug() << "value changed:" << ();});
	 // The return value of subscribe is not saved, and the callback function will only be executed once
     ([&](){qDebug() << "call subscribe:" << ();});
	 // OnValueChanged and addNotifier If the return value is not saved, the callback function will not be executed once.
     auto notify = ([&](){qDebug() << "call Notifier:" << ();});
     (10); qDebug() << "prop =" << ();
     (20); qDebug() << "prop =" << ();
     (30); qDebug() << "prop =" << ();

     return 0;
 }

 #include ""

The output content is as follows:

call subscribe: 5
call Notifier: 10
value changed: 10
prop = 10
call Notifier: 20
value changed: 20
prop = 20
call Notifier: 30
value changed: 30
prop = 30

Note: When using Qt 6.8.1, Qt Creator 15.0.0 to compile the above code, the following error will appear.
include/QtCore/:667:37: error: constexpr variable 'iface<int>' must be initialized by a constant expression
Solution: Open the file and modify the 667-line code inline constexpr QBindableInterface iface = {, just comment out the constexpr modifier. This could be a bug in Qt 6.8.1

refer to:Qt Bindable Properties