From 6c1a9f2b4d4e0d2bcea0989c852ed1c653588154 Mon Sep 17 00:00:00 2001 From: Lars Knoll Date: Thu, 29 Apr 2021 14:08:50 +0200 Subject: [PATCH] Simplify storing of notification objects QPropertyChangeHandler is a templated class and it's argument is a functor. That makes it inherently cumbersome to use the class in any context where the change handler needs to be stored. Introduce a QPropertyNotifier class that stores the functor in a std::function, and add a QProperty::addNotifier() method that can be used instead of onValueChanged(). Also make QPropertyNotifier default constructible. This significantly simplifies the code that needs to be written and makes it possible to store notifications as class members without major hassle. Fixes: QTBUG-92980 Change-Id: Id5b7baec093b9ac0467946cded943d92ad21030b Reviewed-by: Fabian Kosmale --- src/corelib/kernel/qproperty.cpp | 97 +++++++++++++++++-- src/corelib/kernel/qproperty.h | 62 ++++++++++++ src/corelib/kernel/qproperty_p.h | 7 ++ .../kernel/qproperty/tst_qproperty.cpp | 32 ++++++ 4 files changed, 191 insertions(+), 7 deletions(-) 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