diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp index 86a8285bc1..22e9e2d6e0 100644 --- a/src/corelib/kernel/qproperty.cpp +++ b/src/corelib/kernel/qproperty.cpp @@ -957,6 +957,18 @@ QString QPropertyBindingError::description() const \sa onValueChanged() */ +/*! + \fn template QPropertyNotifier QUntypedBindable::addNotifier(Functor f) + + Installs \a f as a change handler. Whenever the underlying property changes, \a f will be called, as + long as the returned \c QPropertyNotifier and the property are kept alive. + + This method is in some cases easier to use than onValueChanged(), as the returned object is not a template. + It can therefore more easily be stored, e.g. as a member in a class. + + \sa onValueChanged(), subscribe() +*/ + /*! \fn QUntypedPropertyBinding QUntypedBindable::binding() const @@ -1247,7 +1259,7 @@ QString QPropertyBindingError::description() const is either called immediately, or deferred, depending on the context. The callback \a f is expected to be a type that has a plain call operator () without any - parameters. This means that you can provide a C++ lambda expression, an std::function + parameters. This means that you can provide a C++ lambda expression, a std::function or even a custom struct with a call operator. The returned property change handler object keeps track of the registration. When it @@ -1261,12 +1273,31 @@ QString QPropertyBindingError::description() const the value of the property changes in the future. On each value change, the handler is either called immediately, or deferred, depending on the context. + The callback \a f is expected to be a type that can be copied and has a plain call + operator() without any parameters. This means that you can provide a C++ lambda expression, + a std::function or even a custom struct with a call operator. + + The returned property change handler object keeps track of the subscription. When it + goes out of scope, the callback is unsubscribed. +*/ + +/*! + \fn QPropertyNotifier QProperty::addNotifier(Functor f) + + Subscribes the given functor \a f as a callback that is called whenever + the value of the property changes. + The callback \a f is expected to be a type that has a plain call operator () without any - parameters. This means that you can provide a C++ lambda expression, an std::function + parameters. This means that you can provide a C++ lambda expression, a std::function or even a custom struct with a call operator. The returned property change handler object keeps track of the subscription. When it goes out of scope, the callback is unsubscribed. + + This method is in some cases easier to use than onValueChanged(), as the returned object is not a template. + It can therefore more easily be stored, e.g. as a member in a class. + + \sa onValueChanged(), subscribe() */ /*! @@ -1664,7 +1695,7 @@ QString QPropertyBindingError::description() const is either called immediately, or deferred, depending on the context. The callback \a f is expected to be a type that has a plain call operator () without any - parameters. This means that you can provide a C++ lambda expression, an std::function + parameters. This means that you can provide a C++ lambda expression, a std::function or even a custom struct with a call operator. The returned property change handler object keeps track of the registration. When it @@ -1679,13 +1710,32 @@ QString QPropertyBindingError::description() const is either called immediately, or deferred, depending on the context. The callback \a f is expected to be a type that has a plain call operator () without any - parameters. This means that you can provide a C++ lambda expression, an std::function + parameters. This means that you can provide a C++ lambda expression, a std::function or even a custom struct with a call operator. The returned property change handler object keeps track of the subscription. When it goes out of scope, the callback is unsubscribed. */ +/*! + \fn template template QPropertyNotifier QObjectBindableProperty::addNotifier(Functor f) + + Subscribes the given functor \a f as a callback that is called whenever + the value of the property changes. + + The callback \a f is expected to be a type that has a plain call operator () without any + parameters. This means that you can provide a C++ lambda expression, a std::function + or even a custom struct with a call operator. + + The returned property change handler object keeps track of the subscription. When it + goes out of scope, the callback is unsubscribed. + + This method is in some cases easier to use than onValueChanged(), as the returned object is not a template. + It can therefore more easily be stored, e.g. as a member in a class. + + \sa onValueChanged(), subscribe() +*/ + /*! \fn template QtPrivate::QPropertyBase &QObjectBindableProperty::propertyBase() const \internal @@ -1698,13 +1748,27 @@ QString QPropertyBindingError::description() const \ingroup tools - QPropertyChangeHandler\ is created when registering a + QPropertyChangeHandler\ is created when registering a callback on a QProperty to listen to changes to the property's value, using QProperty::onValueChanged and QProperty::subscribe. As long as the change handler is alive, the callback remains installed. A handler instance can be transferred between C++ scopes using move semantics. */ +/*! + \class QPropertyNotifier + \inmodule QtCore + \brief The QPropertyNotifier class controls the lifecycle of change callback installed on a QProperty. + + \ingroup tools + + QPropertyNotifier is created when registering a + callback on a QProperty to listen to changes to the property's value, using QProperty::addNotifier. + As long as the change handler is alive, the callback remains installed. + + A handler instance can be transferred between C++ scopes using move semantics. +*/ + /*! \class QPropertyAlias \inmodule QtCore @@ -1881,7 +1945,7 @@ QString QPropertyBindingError::description() const is either called immediately, or deferred, depending on the context. The callback \a f is expected to be a type that has a plain call operator () without any - parameters. This means that you can provide a C++ lambda expression, an std::function + parameters. This means that you can provide a C++ lambda expression, a std::function or even a custom struct with a call operator. The returned property change handler object keeps track of the registration. When it @@ -1896,13 +1960,32 @@ QString QPropertyBindingError::description() const is either called immediately, or deferred, depending on the context. The callback \a f is expected to be a type that has a plain call operator () without any - parameters. This means that you can provide a C++ lambda expression, an std::function + parameters. This means that you can provide a C++ lambda expression, a std::function or even a custom struct with a call operator. The returned property change handler object keeps track of the subscription. When it goes out of scope, the callback is unsubscribed. */ +/*! + \fn template QPropertyNotifier QPropertyAlias::addNotifier(Functor f) + + Subscribes the given functor \a f as a callback that is called whenever + the value of the aliased property changes. + + The callback \a f is expected to be a type that has a plain call operator () without any + parameters. This means that you can provide a C++ lambda expression, a std::function + or even a custom struct with a call operator. + + The returned property change handler object keeps track of the subscription. When it + goes out of scope, the callback is unsubscribed. + + This method is in some cases easier to use than onValueChanged(), as the returned object is not a template. + It can therefore more easily be stored, e.g. as a member in a class. + + \sa onValueChanged(), subscribe() +*/ + /*! \fn template bool QPropertyAlias::isValid() const diff --git a/src/corelib/kernel/qproperty.h b/src/corelib/kernel/qproperty.h index 5a386f857e..5deb8fa9b7 100644 --- a/src/corelib/kernel/qproperty.h +++ b/src/corelib/kernel/qproperty.h @@ -294,6 +294,33 @@ public: } }; +class [[nodiscard]] QPropertyNotifier : public QPropertyObserver +{ + std::function m_handler; +public: + QPropertyNotifier() = default; + template + QPropertyNotifier(Functor handler) + : QPropertyObserver([](QPropertyObserver *self, QUntypedPropertyData *) { + auto This = static_cast(self); + This->m_handler(); + }) + , m_handler(handler) + { + } + + template + QPropertyNotifier(const Property &property, Functor handler) + : QPropertyObserver([](QPropertyObserver *self, QUntypedPropertyData *) { + auto This = static_cast(self); + This->m_handler(); + }) + , m_handler(handler) + { + setSource(property); + } +}; + template class QProperty : public QPropertyData { @@ -442,6 +469,13 @@ public: return onValueChanged(f); } + template + QPropertyNotifier addNotifier(Functor f) + { + static_assert(std::is_invocable_v, "Functor callback must be callable without any parameters"); + return QPropertyNotifier(*this, f); + } + const QtPrivate::QPropertyBindingData &bindingData() const { return d; } private: void notify() @@ -630,6 +664,14 @@ public: return onValueChanged(f); } + template + QPropertyNotifier addNotifier(Functor f) + { + QPropertyNotifier handler(f); + observe(&handler); + return handler; + } + QUntypedPropertyBinding binding() const { if (!isBindable()) { @@ -871,6 +913,12 @@ public: return QBindable(aliasedProperty(), iface).subscribe(f); } + template + QPropertyNotifier addNotifier(Functor f) + { + return QBindable(aliasedProperty(), iface).notify(f); + } + bool isValid() const { return aliasedProperty() != nullptr; @@ -1109,6 +1157,13 @@ public: return onValueChanged(f); } + template + QPropertyNotifier addNotifier(Functor f) + { + static_assert(std::is_invocable_v, "Functor callback must be callable without any parameters"); + return QPropertyNotifier(*this, f); + } + const QtPrivate::QPropertyBindingData &bindingData() const { auto *storage = const_cast(qGetBindingStorage(owner())); @@ -1238,6 +1293,13 @@ public: return onValueChanged(f); } + template + QPropertyNotifier addNotifier(Functor f) + { + static_assert(std::is_invocable_v, "Functor callback must be callable without any parameters"); + return QPropertyNotifier(*this, f); + } + QtPrivate::QPropertyBindingData &bindingData() const { auto *storage = const_cast(qGetBindingStorage(owner())); diff --git a/src/corelib/kernel/qproperty_p.h b/src/corelib/kernel/qproperty_p.h index 7cae247601..e92c8f878a 100644 --- a/src/corelib/kernel/qproperty_p.h +++ b/src/corelib/kernel/qproperty_p.h @@ -570,6 +570,13 @@ public: return onValueChanged(f); } + template + QPropertyNotifier addNotifier(Functor f) + { + static_assert(std::is_invocable_v, "Functor callback must be callable without any parameters"); + return QPropertyNotifier(*this, f); + } + QtPrivate::QPropertyBindingData &bindingData() const { auto *storage = const_cast(qGetBindingStorage(owner())); diff --git a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp index 4195459af5..d1dc0ceacf 100644 --- a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp +++ b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp @@ -108,6 +108,8 @@ private slots: void groupedNotifications(); void groupedNotificationConsistency(); void uninstalledBindingDoesNotEvaluate(); + + void notify(); }; void tst_QProperty::functorBinding() @@ -1774,6 +1776,36 @@ void tst_QProperty::uninstalledBindingDoesNotEvaluate() QCOMPARE(bindingEvaluationCounter, 2); } +void tst_QProperty::notify() +{ + QProperty testProperty(0); + QList recordedValues; + int value = 0; + QPropertyNotifier notifier; + + { + QPropertyNotifier handler = testProperty.addNotifier([&]() { + recordedValues << testProperty; + }); + notifier = testProperty.addNotifier([&]() { + value = testProperty; + }); + + testProperty = 1; + testProperty = 2; + } + QCOMPARE(value, 2); + testProperty = 3; + QCOMPARE(value, 3); + notifier = {}; + testProperty = 4; + QCOMPARE(value, 3); + + QCOMPARE(recordedValues.count(), 2); + QCOMPARE(recordedValues.at(0), 1); + QCOMPARE(recordedValues.at(1), 2); +} + QTEST_MAIN(tst_QProperty); #undef QT_SOURCE_LOCATION_NAMESPACE