Skip to content

ui::Property

Header: winui/core/Property.hpp.

template <typename T> class Property

Derives from ui::PropertyBase.

An observable value: application state written once, from which every display of it is derived through bind() or observe().

T must be equality comparable, because the equality gate is not optional: set() compares before it assigns, so a no-op write notifies nobody and a dependency cycle terminates instead of recursing. Non-copyable and non-movable, since bindings hold a reference to it. A Property may outlive the controls bound to it or be destroyed first, which ends the bindings. UI thread only: set() is not synchronized.

Example:

ui::Property<int> clicks{0};

window.set_content(ui::VerticalStack{
    .spacing  = 12,
    .content  = {
        ui::Text{.text = ui::bind(clicks, [](int n) { return std::format("Clicks: {}", n); })},
        ui::Button{.text = "Click me", .on_click = [&] { ++clicks; }},
        ui::Button{.text = "Reset", .on_click = [&] { clicks = 0; }},
    },
});

value_type

using ui::Property< T >::value_type = T

The stored type.

Property

Property() = default

Holds a value-initialized T.

Property

Property(T value)

Holds value; no observer exists yet, so nothing is notified.

get

const T& get() const noexcept

The current value. The reference is invalidated by the next set().

operator*

const T& operator*() const noexcept

Same as get().

operator->

const T* operator->() const noexcept

Member access to the current value, e.g. title->size().

set

void set(T value, std::source_location where = std::source_location::current())

Assign and, if the value changed (!= by T's operator==), notify every observer synchronously before returning. UI thread only: a build with thread checks reports a write from another thread than the property's first notifying one, naming where (the caller's file and line; leave it defaulted).

operator=

Property& operator=(detail::AssignedValue<T> value)

Same as set(): clicks = 0;, and a thread check report names the caller's line as set()'s does. A braced value of several parts goes through set(): size.set({4, 3});.

operator+=

Property& operator+=(detail::AssignedValue<T> delta)
    requires (!std::same_as<T, bool>) && requires(const T& a, const T& b) { { a + b } -> std::convertible_to<T>; }

Adds delta through set(), so the equality gate applies: count += 5;, log += "Saved\n";. Exists when T + T gives a T, except for bool.

operator-=

Property& operator-=(detail::AssignedValue<T> delta)
    requires (!std::same_as<T, bool>) && requires(const T& a, const T& b) { { a - b } -> std::convertible_to<T>; }

Subtracts delta through set(): remaining -= 1;. Exists when T - T gives a T, except for bool.

operator++

Property& operator++()
    requires requires(T& t) { ++t; }

Adds one through set(): ++clicks;. Exists when T has a ++.

operator--

Property& operator--()
    requires requires(T& t) { --t; }

Subtracts one through set(): --remaining;. Exists when T has a --.

operator++

T operator++(int)
    requires requires(T& t) { ++t; }

As ++clicks, returning the value from before, as ++ on the T itself would.

operator--

T operator--(int)
    requires requires(T& t) { --t; }

As --remaining, returning the value from before.

toggle

void toggle(std::source_location where = std::source_location::current())
    requires std::same_as<T, bool>

Flips a Property<bool> through set(): dark_mode.toggle();.

update

template <typename Fn>
void update(Fn&& fn, std::source_location where = std::source_location::current())
    requires std::invocable<Fn&, T&>

Read-modify-write for values that are awkward to rebuild by hand (a container, a struct with several fields). fn mutates a copy; the equality gate then decides whether anything is notified, so a mutation that changes nothing still costs nothing. where is for the thread check, as in set().

observe

Subscription observe(Callback<void(const T&)> fn) const

Observe changes. The returned Subscription must be kept alive for fn to keep firing; store it on the Element that depends on it (Element::add_subscription) so subscription lifetime is node lifetime. Observers are called newest subscription first. fn is not called for the current value. A caller that wants the initial value reads get(), as Binding does.