Skip to content

ui::Binding

Header: winui/core/Property.hpp.

template <typename T> class Binding

A spec field's value: either a constant or a live binding to a Property, told apart in the source.

A plain value converts implicitly and is a constant, set once. A Property or a Computed written into the field binds it: .text = name follows name from then on, two-way on an editable field. bind() is for the rest: a conversion (ui::bind(clicks, fn)), several sources, and bind_one_way(). name.get() is the current value as a constant. A spec's build() consumes it, subscribing the built control when it is reactive().

Copyable: one binding can go into several fields, and each control that builds from a copy subscribes on its own, so it follows the property for as long as it lives. A two-way binding copied into several editable fields writes back from each of them, and the equality gate keeps them from writing to each other in turn.

Example:

ui::Property<std::string> title{"Untitled"};
ui::Property<int>         clicks{0};

window.set_content(ui::VerticalStack{.content = {
    ui::Text{.text = "static label"}, // a value: set once, never tracked
    ui::Text{.text = title},          // a binding: tracked
    ui::Text{.text = ui::bind(clicks, [](int n) { return std::format("Clicks: {}", n); })},
}});
title = "Report.txt"; // the second Text updates

ui::Binding<std::string> name = ui::bind(title); // one binding, two fields
ui::VerticalStack{.content = {ui::TextBox{.text = name}, ui::Text{.text = name}}};

element_type

using ui::Binding< T >::element_type = typename detail::BindingSource<T>::Element

For a binding of a std::vector, its element type; an empty placeholder type otherwise.

Attach

using ui::Binding< T >::Attach = std::function<Subscription(Callback<void(T)>, T* now)>

The reactive form's subscribe thunk: hands a sink to the source property and returns the resulting Subscription for the built Element to own. When now is not null it first stores the source's value of this moment there; an empty sink only reads.

Write

using ui::Binding< T >::Write = std::function<void(const T&)>

The two-way form's write thunk: stores a value the user entered back into the source.

Binding

Binding() = default

A constant value-initialized T.

Binding

Binding(T value)

A plain value. Implicit, so .text = title works as written.

Binding

template <typename U>
Binding(U&& value)
    requires (!std::same_as<std::remove_cvref_t<U>, T> && !std::same_as<std::remove_cvref_t<U>, Binding> && std::convertible_to<U, T>)

A plain value of another type that converts to T, so a field that is a Binding<std::optional<Color>> takes colors::White (two user-defined conversions in a row otherwise, which C++ refuses), a Binding<float> takes an int and a Binding<std::size_t> a non-negative int, and a Binding<std::string> a string literal. A conversion that would lose the value does not compile: a pointer or a number into a bool field, a floating-point number into an integer one, an integer into a narrower one.

Binding

template <typename A, typename B, typename... Rest>
Binding(A&& a, B&& b, Rest&& ... rest)
    requires (!std::same_as<std::remove_cvref_t<B>, Attach>) && (std::constructible_from<T, A, B, Rest...> || (detail::OptionalValue<T>::optional && std::constructible_from<typename detail::OptionalValue<T>::type, A, B, Rest...>))

A value built in place from a braced list of its constructor's arguments, so a field takes {a, b} as a plain field of its value type would, and an optional field as its value type would.

Binding

Binding(std::initializer_list<element_type> elements)
    requires requires(const detail::BindingSource<T>& source) { source.collection; }

For a binding of a vector: its elements as a braced list, so .items = {"Inbox", "Sent"} needs no std::vector<std::string> spelled out.

Binding

Binding(T initial, Attach attach)

The reactive form. bind() builds it; callers never spell it out.

Binding

Binding(T initial, Attach attach, Write write)

A two-way binding: reactive, and able to write a new value back into its source. What bind() makes from a non-const Property.

Binding

Binding(T initial, Attach attach, Write write, const void* group)
    requires requires(const detail::BindingSource<T>& source) { source.group; }

A two-way binding of a bool that also names the group of choices it is one of; what ui::selects() makes.

Binding

Binding(ObservableVector<element_type>* collection, typename detail::BindingSource<T>::Snapshot snapshot, Attach attach)
    requires requires(const detail::BindingSource<T>& source) { source.collection; }

A binding of a vector that also names the collection it came from; what bind() of a ui::ObservableVector makes. snapshot copies the collection into initial() the first time it is asked; null leaves it empty (an item type that cannot be copied).

Binding

template <typename S>
Binding(S& source)
    requires (detail::is_source<S> && detail::PropertyBindsTo<detail::source_value_t<S>, T>) || detail::is_collection_of<S, T>

A Property, a Computed or an ObservableVector, bound: .text = name is the same as .text = ui::bind(name), .items = todos as .items = ui::bind(todos). A writable Property is two-way on an editable field, as bind() is; a const Property and a Computed are one-way. A source of another type converts as a bind() of it would.

Binding

template <typename U>
Binding(Binding<U> other)
    requires (!std::same_as<U, T>) && std::convertible_to<const U&, T> && (detail::binding_value_converts<U, T>())

A binding of another type that converts to T, still reactive: a Binding<std::string> takes a bind() whose function returns a string literal, a Binding<double> a Property<int>, a Binding<std::optional<bool>> a Property<bool>. The conversions a plain value refuses (a number into a bool, a floating-point number into an integer, an integer into a narrower one) are refused here too.

A two-way binding stays two-way when the value converts back without loss, as between float and double: a Slider's value takes ui::bind() of a Property<float>, and what the user drags is written back as a float. Otherwise the result is one-way, and an editable field (TwoWayField) refuses it at compile time.

Binding

Binding(const Binding&) = default

A copy subscribes on its own when its field is built; see the class comment.

operator=

Binding& operator=(const Binding&) = default

Same as the copy constructor.

Binding

Binding(Binding&& other)

Takes other's value and thunks; other is left a constant.

operator=

Binding& operator=(Binding&& other)

Same as the move constructor.

initial

const T& initial() const

The value as of construction: what a control is initialized with, whether or not this binding is reactive. For a binding of an ObservableVector, a copy of the collection made the first time it is asked, so a field that reads the collection in place (observable()) never makes one.

current

T current() const

The value the source holds right now: what a control that subscribes later than the binding was made starts from, since a property may have changed in between. initial() for a constant.

reactive

bool reactive() const noexcept

True if this came from bind() and will keep delivering values; false for a constant.

subscribe

Subscription subscribe(Callback<void(T)> sink)

Start tracking. Only valid when reactive(); the returned Subscription must outlive the interest in sink. Callers use ui::apply() (ui/spec/Common.hpp) instead, which also sets the initial value.

writable

bool writable() const noexcept

True when the binding can write back into its source: bind() of a non-const Property with no conversion, or one turned into a field type it converts back from without loss.

take_writer

Write take_writer() noexcept

The write thunk, moved out for the control that will call it; empty for a one-way binding. An editable field's build() takes it (ui::write_back()); a display field never does, which is what makes the same bind() one-way there.

observable

auto* observable() const noexcept
    requires requires(const detail::BindingSource<T>& source) { source.collection; }

For a binding of a vector: the ui::ObservableVector it was made from, or nullptr when it came from a plain vector or a Property. A field that can follow single edits reads the collection through this instead of taking whole copies from subscribe().

selection_group

const void* selection_group() const noexcept
    requires requires(const detail::BindingSource<T>& source) { source.group; }

For a binding of a bool made by ui::selects(): the property whose choices it is one of, the same for every binding of that property. Controls bound with the same group form one set of choices (RadioButton). nullptr for any other binding.