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:
James DeLisle 2023-02-28 16:31:13 -05:00
parent de5e0422ca
commit ed6a3ce0af
9 changed files with 43 additions and 47 deletions

View File

@ -13,7 +13,7 @@ BindableSubscription::BindableSubscription(BindableUser *user) : m_user(user)
m_price.setBinding([this] { return qRound(calculateDiscount() * m_duration * basePrice()); }); m_price.setBinding([this] { return qRound(calculateDiscount() * m_duration * basePrice()); });
m_isValid.setBinding([this] { 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 int BindableSubscription::basePrice() const
{ {
if (m_user->country() == BindableUser::None) if (m_user->country() == BindableUser::Country::AnyCountry)
return 0; return 0;
return (m_user->country() == BindableUser::Norway) ? 100 : 80; return (m_user->country() == BindableUser::Country::Norway) ? 100 : 80;
} }

View File

@ -14,7 +14,7 @@ class BindableUser;
class BindableSubscription class BindableSubscription
{ {
public: public:
enum Duration { Monthly = 1, Quarterly = 4, Yearly = 12 }; enum Duration { Monthly = 1, Quarterly = 3, Yearly = 12 };
BindableSubscription(BindableUser *user); BindableSubscription(BindableUser *user);
BindableSubscription(const BindableSubscription &) = delete; BindableSubscription(const BindableSubscription &) = delete;

View File

@ -4,6 +4,7 @@
#ifndef BINDABLEUSER_H #ifndef BINDABLEUSER_H
#define BINDABLEUSER_H #define BINDABLEUSER_H
#include <QLocale>
#include <QProperty> #include <QProperty>
//! [bindable-user-class] //! [bindable-user-class]
@ -11,13 +12,9 @@
class BindableUser class BindableUser
{ {
public: public:
enum Country { using Country = QLocale::Territory;
None,
Finland,
Germany,
Norway,
};
public:
BindableUser() = default; BindableUser() = default;
BindableUser(const BindableUser &) = delete; BindableUser(const BindableUser &) = delete;
@ -30,7 +27,7 @@ public:
QBindable<int> bindableAge() { return &m_age; } QBindable<int> bindableAge() { return &m_age; }
private: private:
QProperty<Country> m_country { None }; QProperty<Country> m_country { QLocale::AnyTerritory };
QProperty<int> m_age { 0 }; QProperty<int> m_age { 0 };
}; };

View File

@ -37,15 +37,15 @@ int main(int argc, char *argv[])
// Initialize user data // Initialize user data
QPushButton *germany = w.findChild<QPushButton *>("btnGermany"); QPushButton *germany = w.findChild<QPushButton *>("btnGermany");
QObject::connect(germany, &QPushButton::clicked, [&] { QObject::connect(germany, &QPushButton::clicked, [&] {
user.setCountry(BindableUser::Germany); user.setCountry(BindableUser::Country::Germany);
}); });
QPushButton *finland = w.findChild<QPushButton *>("btnFinland"); QPushButton *finland = w.findChild<QPushButton *>("btnFinland");
QObject::connect(finland, &QPushButton::clicked, [&] { QObject::connect(finland, &QPushButton::clicked, [&] {
user.setCountry(BindableUser::Finland); user.setCountry(BindableUser::Country::Finland);
}); });
QPushButton *norway = w.findChild<QPushButton *>("btnNorway"); QPushButton *norway = w.findChild<QPushButton *>("btnNorway");
QObject::connect(norway, &QPushButton::clicked, [&] { QObject::connect(norway, &QPushButton::clicked, [&] {
user.setCountry(BindableUser::Norway); user.setCountry(BindableUser::Country::Norway);
}); });
QSpinBox *ageSpinBox = w.findChild<QSpinBox *>("ageSpinBox"); QSpinBox *ageSpinBox = w.findChild<QSpinBox *>("ageSpinBox");
@ -58,7 +58,8 @@ int main(int argc, char *argv[])
// Track price changes // Track price changes
//! [update-ui] //! [update-ui]
auto priceChangeHandler = subscription.bindablePrice().subscribe([&] { 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([&] { auto priceValidHandler = subscription.bindableIsValid().subscribe([&] {

View File

@ -15,7 +15,7 @@
\image bindable_properties_example.png \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. Let's first consider the usual pre-Qt 6 implementation.
To model the subscription service the \c Subscription class is used: 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 \note Both methods need to check if the data is actually changed and
only then emit the signals. \c setDuration() also needs to recalculate 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 The \c Subscription is not valid unless the user has a valid country and
age, so the validity is updated in the following way: age, so the validity is updated in the following way:
@ -63,25 +63,25 @@
\snippet bindableproperties/subscription/user.cpp user-setters \snippet bindableproperties/subscription/user.cpp user-setters
In the \c main() function we initialize instances of \c User and In the \c main() function we initialize instances of \c User and
\c Subsrcription: \c Subscription:
\snippet bindableproperties/subscription/main.cpp init \snippet bindableproperties/subscription/main.cpp init
And do the proper signal-slot connections, to update the \c user and And do the proper signal-slot connections to update the \c user and
\c subsrcription data when UI elements change. That is straightforward, \c subscription data when UI elements change. That is straightforward,
so we will skip this part. 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. in the UI when the price changes.
\snippet bindableproperties/subscription/main.cpp connect-price-changed \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. display if the subscription isn't valid.
\snippet bindableproperties/subscription/main.cpp connect-validity-changed \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() country and age, we also need to connect to the \c User::countryChanged()
and \c User::ageChanged() signals and update \c subscription accordingly. and \c User::ageChanged() signals and update \c subscription accordingly.
@ -90,12 +90,12 @@
This works, but there are some problems: This works, but there are some problems:
\list \list
\li There's a lot of boilerplate code for the signal-slot connections, \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 in order to properly track changes to both \c user and \c subscription.
the dependencies of the price changes, we need to remember to emit the 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 corresponding notifier signals, recalculate the price, and update it in
the UI. 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 need to add more signal-slot connections and make sure all the dependencies
are properly updated whenever any of them changes. The overall complexity are properly updated whenever any of them changes. The overall complexity
will grow, and the code will become harder to maintain. 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 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, 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: bindable properties:
\snippet bindableproperties/bindablesubscription/bindablesubscription.h bindable-subscription-class \snippet bindableproperties/bindablesubscription/bindablesubscription.h bindable-subscription-class
@ -156,7 +156,7 @@
changes the value. The subscriptions will stay alive as long as the changes the value. The subscriptions will stay alive as long as the
corresponding handlers are alive. 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 \c BindableUser are disabled, since it's not defined what should happen
with their bindings when copying. with their bindings when copying.
@ -166,7 +166,7 @@
\list \list
\li The boilerplate code for the signal-slot connections is removed, the \li The boilerplate code for the signal-slot connections is removed, the
dependencies are now tracked automatically. 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 will only require adding the corresponding bindable properties and setting
the binding expressions that reflect the relationships between each other. the binding expressions that reflect the relationships between each other.
\li The \c Subscription and \c User classes don't depend on the metaobject \li The \c Subscription and \c User classes don't depend on the metaobject

View File

@ -40,15 +40,15 @@ int main(int argc, char *argv[])
// Initialize user data // Initialize user data
QPushButton *germany = w.findChild<QPushButton *>("btnGermany"); QPushButton *germany = w.findChild<QPushButton *>("btnGermany");
QObject::connect(germany, &QPushButton::clicked, &user, [&] { QObject::connect(germany, &QPushButton::clicked, &user, [&] {
user.setCountry(User::Germany); user.setCountry(User::Country::Germany);
}); });
QPushButton *finland = w.findChild<QPushButton *>("btnFinland"); QPushButton *finland = w.findChild<QPushButton *>("btnFinland");
QObject::connect(finland, &QPushButton::clicked, &user, [&] { QObject::connect(finland, &QPushButton::clicked, &user, [&] {
user.setCountry(User::Finland); user.setCountry(User::Country::Finland);
}); });
QPushButton *norway = w.findChild<QPushButton *>("btnNorway"); QPushButton *norway = w.findChild<QPushButton *>("btnNorway");
QObject::connect(norway, &QPushButton::clicked, &user, [&] { QObject::connect(norway, &QPushButton::clicked, &user, [&] {
user.setCountry(User::Norway); user.setCountry(User::Country::Norway);
}); });
QSpinBox *ageSpinBox = w.findChild<QSpinBox *>("ageSpinBox"); QSpinBox *ageSpinBox = w.findChild<QSpinBox *>("ageSpinBox");
@ -65,7 +65,8 @@ int main(int argc, char *argv[])
//! [connect-price-changed] //! [connect-price-changed]
QObject::connect(&subscription, &Subscription::priceChanged, [&] { 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] //! [connect-price-changed]

View File

@ -57,10 +57,10 @@ double Subscription::calculateDiscount() const
int Subscription::basePrice() const int Subscription::basePrice() const
{ {
if (m_user->country() == User::None) if (m_user->country() == User::Country::AnyTerritory)
return 0; return 0;
return (m_user->country() == User::Norway) ? 100 : 80; return (m_user->country() == User::Country::Norway) ? 100 : 80;
} }
//! [calculate-base-price] //! [calculate-base-price]
@ -70,7 +70,7 @@ int Subscription::basePrice() const
void Subscription::updateValidity() void Subscription::updateValidity()
{ {
bool isValid = m_isValid; 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) if (m_isValid != isValid)
emit isValidChanged(); emit isValidChanged();

View File

@ -15,7 +15,7 @@ class Subscription : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
enum Duration { Monthly = 1, Quarterly = 4, Yearly = 12 }; enum Duration { Monthly = 1, Quarterly = 3, Yearly = 12 };
Subscription(User *user); Subscription(User *user);

View File

@ -4,6 +4,7 @@
#ifndef USER_H #ifndef USER_H
#define USER_H #define USER_H
#include <QLocale>
#include <QObject> #include <QObject>
//! [user-class] //! [user-class]
@ -13,13 +14,9 @@ class User : public QObject
Q_OBJECT Q_OBJECT
public: public:
enum Country { using Country = QLocale::Territory;
None,
Finland,
Germany,
Norway,
};
public:
Country country() const { return m_country; } Country country() const { return m_country; }
void setCountry(Country country); void setCountry(Country country);
@ -31,8 +28,8 @@ signals:
void ageChanged(); void ageChanged();
private: private:
Country m_country = Country::None; Country m_country { QLocale::AnyTerritory };
int m_age = 0; int m_age { 0 };
}; };
//! [user-class] //! [user-class]