Skip to content

ui::ObservableVector

Header: winui/core/ObservableVector.hpp.

template <typename T> class ObservableVector

Derives from ui::PropertyBase.

A collection that reports each edit: what was inserted, removed, replaced or moved, and where.

It is the collection counterpart of ui::Property. A Property holding a std::vector can only say "changed"; an ObservableVector says what changed, so a ui::ForEach or a list view bound to it builds one row for an inserted item, destroys one for a removed item and leaves every other row, and its state, alone.

Each item lives at its own address, which stays the same for as long as the item is in the collection, whatever is inserted or removed around it. A row may therefore keep a reference to its item and bind to the item's own Property members. Positions do shift, so a row should not keep its index; index_of() finds an item's current position.

Observers are held the same way a Property holds them: the collection or the observer may be destroyed first. Destroying the collection reports a Reset to empty, so a tree still showing it shows nothing afterwards. Non-copyable and non-movable; UI-thread only.

Example:

struct Todo
{
    explicit Todo(std::string text) : title{std::move(text)} {}
    ui::Property<std::string> title;
    ui::Property<bool>        done{false};
};
ui::ObservableVector<Todo> todos;

window.set_content(ui::VerticalStack{.content = {
    ui::Button{.text = "Add", .on_click = [&] { todos.emplace_back("New task"); }},
    ui::ForEach<Todo>{
        .items = todos,
        .item  = [&todos](Todo& todo) -> ui::Spec
        {
            return ui::HorizontalStack{.spacing = 8, .content = {
                ui::ToggleSwitch{.checked = todo.done},
                ui::Text{.text = todo.title},
                ui::Button{.text = "Remove", .on_click = [&todos, &todo] { todos.remove(todo); }},
            }};
        },
    },
}});

value_type

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

The item type.

iterator

using ui::ObservableVector< T >::iterator = Iterator<false>

Iterates the items as T&, in order.

const_iterator

using ui::ObservableVector< T >::const_iterator = Iterator<true>

Iterates the items as const T&, in order.

ObservableVector

ObservableVector() = default

An empty collection.

ObservableVector

ObservableVector(std::initializer_list<T> items)
    requires std::copy_constructible<T>

Holds a copy of each of items; no observer exists yet, so nothing is notified.

~ObservableVector

~ObservableVector()

Reports a Reset to empty, then leaves every remaining Subscription dead.

size

std::size_t size() const noexcept

How many items the collection holds.

empty

bool empty() const noexcept

True when the collection holds no items.

count

const Property<std::size_t>& count() const noexcept

How many items the collection holds, as a Property, so a binding follows it: an empty-state text that shows while there is nothing, a count in a heading. It changes after the collection's own observers were told.

Example:

ui::ObservableVector<std::string> files;
window.set_content(ui::Text{
    .text    = "No files yet",
    .visible = ui::bind(files.count(), [](std::size_t n) { return n == 0; }),
});

operator[]

T& operator[](std::size_t index)

The item at index, which must be less than size(). The reference stays valid until that item is removed or replaced.

operator[]

const T& operator[](std::size_t index) const

The item at index, which must be less than size().

front

T& front()

The first item; the collection must not be empty.

front

const T& front() const

The first item; the collection must not be empty.

back

T& back()

The last item; the collection must not be empty.

back

const T& back() const

The last item; the collection must not be empty.

index_of

std::optional<std::size_t> index_of(const T& item) const

The position of item, found by its address; unset when it is not an item of this collection.

begin

iterator begin() noexcept

The first item, for iterating as T&.

end

iterator end() noexcept

One past the last item.

begin

const_iterator begin() const noexcept

The first item, for iterating as const T&.

end

const_iterator end() const noexcept

One past the last item.

push_back

T& push_back(T item, std::source_location where = std::source_location::current())
    requires std::move_constructible<T>

Appends item and returns it in its place.

emplace_back

template <typename... Args>
T& emplace_back(Args&& ... args)
    requires std::constructible_from<T, Args...>

Appends an item constructed in place from args, for an item type that cannot be moved (one holding a Property), and returns it.

insert

T& insert(std::size_t index, T item, std::source_location where = std::source_location::current())
    requires std::move_constructible<T>

Inserts item before position index (clamped to the end) and returns it in its place.

emplace

template <typename... Args>
T& emplace(std::size_t index, Args&& ... args)
    requires std::constructible_from<T, Args...>

Inserts an item constructed in place from args before position index (clamped to the end) and returns it.

erase

void erase(std::size_t index, std::size_t count = 1, std::source_location where = std::source_location::current())

Removes count items starting at index; a range reaching past the end is cut short. The items are destroyed after the observers have been told.

remove

bool remove(const T& item, std::source_location where = std::source_location::current())

Removes item, found by its address. False when it is not an item of this collection.

erase_if

template <typename Predicate>
std::size_t erase_if(Predicate predicate, std::source_location where = std::source_location::current())
    requires std::predicate<Predicate&, const T&>

Removes every item for which predicate answers true, and returns how many went. Each run of neighboring matches is reported as one Removed, from the back of the collection to the front. predicate is asked about every item before the first one goes.

replace

T& replace(std::size_t index, T item, std::source_location where = std::source_location::current())
    requires std::move_constructible<T>

Replaces the item at index with item; whatever was built for the old one is built again. For an item that holds Properties, set those instead: nothing needs rebuilding.

move

void move(std::size_t from, std::size_t to, std::source_location where = std::source_location::current())

Moves the item at from to position to, both less than size(). The item keeps its address, and what was built for it stays as it is.

clear

void clear(std::source_location where = std::source_location::current())

Removes every item, reported as one Reset.

assign

template <typename Range>
void assign(const Range& items, std::source_location where = std::source_location::current())
    requires requires(const Range& range) { { *std::begin(range) } -> std::convertible_to<T>; }

Replaces the whole content with a copy of each of items, reported as one Reset. Nothing is reported when the collection was empty and stays empty.

assign

void assign(std::initializer_list<T> items, std::source_location where = std::source_location::current())
    requires std::copy_constructible<T>

Replaces the whole content with a copy of each of items, reported as one Reset.

batch

template <typename Edits>
void batch(Edits edits, std::source_location where = std::source_location::current())
    requires std::invocable<Edits&>

Runs edits, which edit this collection, and reports everything they did as one Reset when they return, instead of one change per edit. For a bulk edit (an undo step, a sort, a reload) whose rows are cheaper to build again once than to follow edit by edit. A batch inside a batch reports once, when the outermost returns. Items removed or replaced during the batch are destroyed after the Reset was reported, so nothing built for them dangles. Nothing is reported when edits changed nothing.

Example:

ui::ObservableVector<std::string> pages{"Intro", "Setup", "Usage", "Cover"};
pages.batch([&] {
    std::string moved = pages[3];
    pages.erase(3);
    pages.insert(0, std::move(moved));
});

observe

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

Observe changes. The returned Subscription must be kept alive for fn to keep firing; an Element keeps it with Element::add_subscription(). fn runs after the collection has changed, and is not called for the current content.