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<void()>, 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 <fabian.kosmale@qt.io>
This commit is contained in:
Lars Knoll 2021-04-29 14:08:50 +02:00
parent de15836dbf
commit 6c1a9f2b4d
4 changed files with 191 additions and 7 deletions

View File

@ -957,6 +957,18 @@ QString QPropertyBindingError::description() const
\sa onValueChanged()
*/
/*!
\fn template<typename Functor> 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<T>::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 <typename Class, typename T, auto offset, auto Callback> template <typename Functor> QPropertyNotifier QObjectBindableProperty<Class, T, offset, Callback>::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 <typename T> QtPrivate::QPropertyBase &QObjectBindableProperty<Class, T, offset, Callback>::propertyBase() const
\internal
@ -1698,13 +1748,27 @@ QString QPropertyBindingError::description() const
\ingroup tools
QPropertyChangeHandler\<PropertyType, Functor\> is created when registering a
QPropertyChangeHandler\<Functor\> 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 <typename T> QPropertyNotifier QPropertyAlias<T>::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 <typename T> bool QPropertyAlias<T>::isValid() const

View File

@ -294,6 +294,33 @@ public:
}
};
class [[nodiscard]] QPropertyNotifier : public QPropertyObserver
{
std::function<void()> m_handler;
public:
QPropertyNotifier() = default;
template<typename Functor>
QPropertyNotifier(Functor handler)
: QPropertyObserver([](QPropertyObserver *self, QUntypedPropertyData *) {
auto This = static_cast<QPropertyNotifier *>(self);
This->m_handler();
})
, m_handler(handler)
{
}
template<typename Functor, typename Property, typename = typename Property::InheritsQUntypedPropertyData>
QPropertyNotifier(const Property &property, Functor handler)
: QPropertyObserver([](QPropertyObserver *self, QUntypedPropertyData *) {
auto This = static_cast<QPropertyNotifier *>(self);
This->m_handler();
})
, m_handler(handler)
{
setSource(property);
}
};
template <typename T>
class QProperty : public QPropertyData<T>
{
@ -442,6 +469,13 @@ public:
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);
}
const QtPrivate::QPropertyBindingData &bindingData() const { return d; }
private:
void notify()
@ -630,6 +664,14 @@ public:
return onValueChanged(f);
}
template<typename Functor>
QPropertyNotifier addNotifier(Functor f)
{
QPropertyNotifier handler(f);
observe(&handler);
return handler;
}
QUntypedPropertyBinding binding() const
{
if (!isBindable()) {
@ -871,6 +913,12 @@ public:
return QBindable<T>(aliasedProperty(), iface).subscribe(f);
}
template<typename Functor>
QPropertyNotifier addNotifier(Functor f)
{
return QBindable<T>(aliasedProperty(), iface).notify(f);
}
bool isValid() const
{
return aliasedProperty() != nullptr;
@ -1109,6 +1157,13 @@ public:
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);
}
const QtPrivate::QPropertyBindingData &bindingData() const
{
auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner()));
@ -1238,6 +1293,13 @@ public:
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);
}
QtPrivate::QPropertyBindingData &bindingData() const
{
auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner()));

View File

@ -570,6 +570,13 @@ public:
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);
}
QtPrivate::QPropertyBindingData &bindingData() const
{
auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner()));

View File

@ -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<int> testProperty(0);
QList<int> 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