Update the bindable properties example
- Fix the number of months in each duration - Move the user Country enum to use QLocale::Territory - Properly calculate the cost per month to match the UI label - Use QLocale to format the price display text - Fix some misspellings and grammar in the doc Pick-to: 6.2 6.5 Change-Id: I78a64f344073070cd94d5cb4a8a4c7c13afa337f Reviewed-by: Paul Wicking <paul.wicking@qt.io>
This commit is contained in:
parent
de5e0422ca
commit
ed6a3ce0af
@ -13,7 +13,7 @@ BindableSubscription::BindableSubscription(BindableUser *user) : m_user(user)
|
||||
m_price.setBinding([this] { return qRound(calculateDiscount() * m_duration * basePrice()); });
|
||||
|
||||
m_isValid.setBinding([this] {
|
||||
return m_user->country() != BindableUser::None && m_user->age() > 12;
|
||||
return m_user->country() != BindableUser::Country::AnyCountry && m_user->age() > 12;
|
||||
});
|
||||
}
|
||||
|
||||
@ -44,8 +44,8 @@ double BindableSubscription::calculateDiscount() const
|
||||
|
||||
int BindableSubscription::basePrice() const
|
||||
{
|
||||
if (m_user->country() == BindableUser::None)
|
||||
if (m_user->country() == BindableUser::Country::AnyCountry)
|
||||
return 0;
|
||||
|
||||
return (m_user->country() == BindableUser::Norway) ? 100 : 80;
|
||||
return (m_user->country() == BindableUser::Country::Norway) ? 100 : 80;
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ class BindableUser;
|
||||
class BindableSubscription
|
||||
{
|
||||
public:
|
||||
enum Duration { Monthly = 1, Quarterly = 4, Yearly = 12 };
|
||||
enum Duration { Monthly = 1, Quarterly = 3, Yearly = 12 };
|
||||
|
||||
BindableSubscription(BindableUser *user);
|
||||
BindableSubscription(const BindableSubscription &) = delete;
|
||||
|
@ -4,6 +4,7 @@
|
||||
#ifndef BINDABLEUSER_H
|
||||
#define BINDABLEUSER_H
|
||||
|
||||
#include <QLocale>
|
||||
#include <QProperty>
|
||||
|
||||
//! [bindable-user-class]
|
||||
@ -11,13 +12,9 @@
|
||||
class BindableUser
|
||||
{
|
||||
public:
|
||||
enum Country {
|
||||
None,
|
||||
Finland,
|
||||
Germany,
|
||||
Norway,
|
||||
};
|
||||
using Country = QLocale::Territory;
|
||||
|
||||
public:
|
||||
BindableUser() = default;
|
||||
BindableUser(const BindableUser &) = delete;
|
||||
|
||||
@ -30,7 +27,7 @@ public:
|
||||
QBindable<int> bindableAge() { return &m_age; }
|
||||
|
||||
private:
|
||||
QProperty<Country> m_country { None };
|
||||
QProperty<Country> m_country { QLocale::AnyTerritory };
|
||||
QProperty<int> m_age { 0 };
|
||||
};
|
||||
|
||||
|
@ -37,15 +37,15 @@ int main(int argc, char *argv[])
|
||||
// Initialize user data
|
||||
QPushButton *germany = w.findChild<QPushButton *>("btnGermany");
|
||||
QObject::connect(germany, &QPushButton::clicked, [&] {
|
||||
user.setCountry(BindableUser::Germany);
|
||||
user.setCountry(BindableUser::Country::Germany);
|
||||
});
|
||||
QPushButton *finland = w.findChild<QPushButton *>("btnFinland");
|
||||
QObject::connect(finland, &QPushButton::clicked, [&] {
|
||||
user.setCountry(BindableUser::Finland);
|
||||
user.setCountry(BindableUser::Country::Finland);
|
||||
});
|
||||
QPushButton *norway = w.findChild<QPushButton *>("btnNorway");
|
||||
QObject::connect(norway, &QPushButton::clicked, [&] {
|
||||
user.setCountry(BindableUser::Norway);
|
||||
user.setCountry(BindableUser::Country::Norway);
|
||||
});
|
||||
|
||||
QSpinBox *ageSpinBox = w.findChild<QSpinBox *>("ageSpinBox");
|
||||
@ -58,7 +58,8 @@ int main(int argc, char *argv[])
|
||||
// Track price changes
|
||||
//! [update-ui]
|
||||
auto priceChangeHandler = subscription.bindablePrice().subscribe([&] {
|
||||
priceDisplay->setText(QString::number(subscription.price()));
|
||||
QLocale lc{QLocale::AnyLanguage, user.country()};
|
||||
priceDisplay->setText(lc.toCurrencyString(subscription.price() / subscription.duration()));
|
||||
});
|
||||
|
||||
auto priceValidHandler = subscription.bindableIsValid().subscribe([&] {
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
\image bindable_properties_example.png
|
||||
|
||||
\section1 Modelling Subscription System with Signal/Slot Approach
|
||||
\section1 Modeling Subscription System with Signal/Slot Approach
|
||||
|
||||
Let's first consider the usual pre-Qt 6 implementation.
|
||||
To model the subscription service the \c Subscription class is used:
|
||||
@ -48,7 +48,7 @@
|
||||
|
||||
\note Both methods need to check if the data is actually changed and
|
||||
only then emit the signals. \c setDuration() also needs to recalculate
|
||||
the price, when the duration has changed.
|
||||
the price when the duration has changed.
|
||||
|
||||
The \c Subscription is not valid unless the user has a valid country and
|
||||
age, so the validity is updated in the following way:
|
||||
@ -63,25 +63,25 @@
|
||||
\snippet bindableproperties/subscription/user.cpp user-setters
|
||||
|
||||
In the \c main() function we initialize instances of \c User and
|
||||
\c Subsrcription:
|
||||
\c Subscription:
|
||||
|
||||
\snippet bindableproperties/subscription/main.cpp init
|
||||
|
||||
And do the proper signal-slot connections, to update the \c user and
|
||||
\c subsrcription data when UI elements change. That is straightforward,
|
||||
And do the proper signal-slot connections to update the \c user and
|
||||
\c subscription data when UI elements change. That is straightforward,
|
||||
so we will skip this part.
|
||||
|
||||
Next, we connect to \c Subscription::priceChanged(), to update the price
|
||||
Next, we connect to \c Subscription::priceChanged() to update the price
|
||||
in the UI when the price changes.
|
||||
|
||||
\snippet bindableproperties/subscription/main.cpp connect-price-changed
|
||||
|
||||
We also connect to \c Subscription::isValidChanged(), to disable the price
|
||||
We also connect to \c Subscription::isValidChanged() to disable the price
|
||||
display if the subscription isn't valid.
|
||||
|
||||
\snippet bindableproperties/subscription/main.cpp connect-validity-changed
|
||||
|
||||
Because the subsrcription price and validity also depend on the user's
|
||||
Because the subscription price and validity also depend on the user's
|
||||
country and age, we also need to connect to the \c User::countryChanged()
|
||||
and \c User::ageChanged() signals and update \c subscription accordingly.
|
||||
|
||||
@ -90,12 +90,12 @@
|
||||
This works, but there are some problems:
|
||||
|
||||
\list
|
||||
\li There's a lot of boilerplate code for the signal-slot connections,
|
||||
to be able to react to changes to \c user or \c subscription. If any of
|
||||
the dependencies of the price changes, we need to remember to emit the
|
||||
corresponding notifier signals, to recalculate the price and update it in
|
||||
\li There's a lot of boilerplate code for the signal-slot connections
|
||||
in order to properly track changes to both \c user and \c subscription.
|
||||
If any of the dependencies of the price changes, we need to remember to emit the
|
||||
corresponding notifier signals, recalculate the price, and update it in
|
||||
the UI.
|
||||
\li If more dependencies for price calculation are added in future, we'll
|
||||
\li If more dependencies for price calculation are added in the future, we'll
|
||||
need to add more signal-slot connections and make sure all the dependencies
|
||||
are properly updated whenever any of them changes. The overall complexity
|
||||
will grow, and the code will become harder to maintain.
|
||||
@ -109,7 +109,7 @@
|
||||
|
||||
Now let's see how the \l {Qt Bindable Properties} can help to solve the
|
||||
same problem. First, let's have a look at the \c BindableSubscription class,
|
||||
which is similar to the \c Subscription class, but is implemented using the
|
||||
which is similar to the \c Subscription class, but is implemented using
|
||||
bindable properties:
|
||||
|
||||
\snippet bindableproperties/bindablesubscription/bindablesubscription.h bindable-subscription-class
|
||||
@ -156,7 +156,7 @@
|
||||
changes the value. The subscriptions will stay alive as long as the
|
||||
corresponding handlers are alive.
|
||||
|
||||
Also note that the copy constructors of both \c BindableSubsrciption and
|
||||
Also note that the copy constructors of both \c BindableSubscription and
|
||||
\c BindableUser are disabled, since it's not defined what should happen
|
||||
with their bindings when copying.
|
||||
|
||||
@ -166,7 +166,7 @@
|
||||
\list
|
||||
\li The boilerplate code for the signal-slot connections is removed, the
|
||||
dependencies are now tracked automatically.
|
||||
\li The code is easier to maintain. Adding more dependencies in future
|
||||
\li The code is easier to maintain. Adding more dependencies in the future
|
||||
will only require adding the corresponding bindable properties and setting
|
||||
the binding expressions that reflect the relationships between each other.
|
||||
\li The \c Subscription and \c User classes don't depend on the metaobject
|
||||
|
@ -40,15 +40,15 @@ int main(int argc, char *argv[])
|
||||
// Initialize user data
|
||||
QPushButton *germany = w.findChild<QPushButton *>("btnGermany");
|
||||
QObject::connect(germany, &QPushButton::clicked, &user, [&] {
|
||||
user.setCountry(User::Germany);
|
||||
user.setCountry(User::Country::Germany);
|
||||
});
|
||||
QPushButton *finland = w.findChild<QPushButton *>("btnFinland");
|
||||
QObject::connect(finland, &QPushButton::clicked, &user, [&] {
|
||||
user.setCountry(User::Finland);
|
||||
user.setCountry(User::Country::Finland);
|
||||
});
|
||||
QPushButton *norway = w.findChild<QPushButton *>("btnNorway");
|
||||
QObject::connect(norway, &QPushButton::clicked, &user, [&] {
|
||||
user.setCountry(User::Norway);
|
||||
user.setCountry(User::Country::Norway);
|
||||
});
|
||||
|
||||
QSpinBox *ageSpinBox = w.findChild<QSpinBox *>("ageSpinBox");
|
||||
@ -65,7 +65,8 @@ int main(int argc, char *argv[])
|
||||
|
||||
//! [connect-price-changed]
|
||||
QObject::connect(&subscription, &Subscription::priceChanged, [&] {
|
||||
priceDisplay->setText(QString::number(subscription.price()));
|
||||
QLocale lc{QLocale::AnyLanguage, user.country()};
|
||||
priceDisplay->setText(lc.toCurrencyString(subscription.price() / subscription.duration()));
|
||||
});
|
||||
//! [connect-price-changed]
|
||||
|
||||
|
@ -57,10 +57,10 @@ double Subscription::calculateDiscount() const
|
||||
|
||||
int Subscription::basePrice() const
|
||||
{
|
||||
if (m_user->country() == User::None)
|
||||
if (m_user->country() == User::Country::AnyTerritory)
|
||||
return 0;
|
||||
|
||||
return (m_user->country() == User::Norway) ? 100 : 80;
|
||||
return (m_user->country() == User::Country::Norway) ? 100 : 80;
|
||||
}
|
||||
|
||||
//! [calculate-base-price]
|
||||
@ -70,7 +70,7 @@ int Subscription::basePrice() const
|
||||
void Subscription::updateValidity()
|
||||
{
|
||||
bool isValid = m_isValid;
|
||||
m_isValid = m_user->country() != User::None && m_user->age() > 12;
|
||||
m_isValid = m_user->country() != User::Country::AnyTerritory && m_user->age() > 12;
|
||||
|
||||
if (m_isValid != isValid)
|
||||
emit isValidChanged();
|
||||
|
@ -15,7 +15,7 @@ class Subscription : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Duration { Monthly = 1, Quarterly = 4, Yearly = 12 };
|
||||
enum Duration { Monthly = 1, Quarterly = 3, Yearly = 12 };
|
||||
|
||||
Subscription(User *user);
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
#ifndef USER_H
|
||||
#define USER_H
|
||||
|
||||
#include <QLocale>
|
||||
#include <QObject>
|
||||
|
||||
//! [user-class]
|
||||
@ -13,13 +14,9 @@ class User : public QObject
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Country {
|
||||
None,
|
||||
Finland,
|
||||
Germany,
|
||||
Norway,
|
||||
};
|
||||
using Country = QLocale::Territory;
|
||||
|
||||
public:
|
||||
Country country() const { return m_country; }
|
||||
void setCountry(Country country);
|
||||
|
||||
@ -31,8 +28,8 @@ signals:
|
||||
void ageChanged();
|
||||
|
||||
private:
|
||||
Country m_country = Country::None;
|
||||
int m_age = 0;
|
||||
Country m_country { QLocale::AnyTerritory };
|
||||
int m_age { 0 };
|
||||
};
|
||||
|
||||
//! [user-class]
|
||||
|
Loading…
Reference in New Issue
Block a user