Skip to content

ListView

Header: winui/ui/collections/ListView.hpp. Spec: winui/ui/collections/ListView.hpp.

A vertical, virtualized, selectable list of templated items.

Each item sits in a container with rounded chrome: a backplate for hover and press, a 3 pixel accent selection indicator in Single and Extended mode, and a check box per item in Multiple mode. Items, templates, selection, groups, header and footer, drag reordering and scrolling come from ListViewBase.

Example:

// Declarative: the items and how one row looks (plain strings need no `.item`).
std::vector<std::string> fruits{"Apple", "Banana", "Cherry"};
ui::Control<ui::ListView<std::string>> list;
window.set_content(ui::ListView<std::string>{
    .items                = fruits,
    .selection_mode       = ui::SelectionMode::Single,
    .height               = 240.0f,
    .on_selection_changed = [&](std::optional<std::string> fruit)
    { std::println("selected {}", fruit ? *fruit : "nothing"); },
    .control              = list,
    .item                 = [](const std::string& fruit) -> ui::Spec {
        return ui::Text{.text = fruit};
    },
});

// Imperative:
auto stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto names = std::make_unique<ui::ListViewControl>();
names->set_items({"One", "Two", "Three"});
names->set_item_click_enabled(true);
names->on_item_click([](std::size_t i) { std::println("clicked {}", i); });
stack->add(std::move(names));

ui::ListView

A virtualized, selectable list of rows. Builds a ui::ListViewControl.

.items is the collection and .item is how one row looks. The handlers receive the item they are about. Only rows in view are realized, so .item runs per visible row. An item type that is text (std::string) needs no .item.

.items takes the same three sources as ui::ForEach, and the list follows the first two:

  • A ui::ObservableVector: every insert, removal and move reaches the list as that change, and .item may take the item as T&.
  • A ui::Property<std::vector<T>>: with .key, a reorder is followed as moves, so the selection stays on its item through a sort or a filter. A change that needs very many moves, such as a shuffle of several thousand items, arrives as one reset instead, which rebuilds the rows and clears the selection. Without .key, the rows before and after an edit are kept.
  • A plain std::vector<T> or a braced list, copied once.

A reorder by the user (can_reorder_items) is written back. An ObservableVector is moved in place, and a bound Property is assigned the reordered vector.

Name the item type, as in ui::ListView<Contact>. C++ deduces a spec's type only from the field's own type, which a Property or an ObservableVector is not. The control works with indices, so through .control an item is its position in the collection.

Example:

struct Contact
{
    int         id = 0;
    std::string name;
    std::string company;
    friend bool operator==(const Contact&, const Contact&) = default;
};
ui::Property<std::vector<Contact>> contacts{{{1, "Ada Lovelace", "Analytical"}, {2, "Grace Hopper", "Navy"}}};
ui::Property<std::string>          status{};

window.set_content(ui::ListView<Contact>{
    .items                = contacts,
    .key                  = &Contact::id,
    .height               = 400.0f,
    .on_selection_changed = [&](std::optional<Contact> contact)
    { status = contact ? "Selected " + contact->name : ""; },
    .item                 = [](const Contact& contact) -> ui::Spec
    {
        return ui::VerticalStack{.content = {
            ui::Text{.text = contact.name},
            ui::Text{.text = contact.company, .role = ui::TextRole::Caption},
        }};
    },
});

// Plain text needs no template.
ui::ListView<std::string>{.items = {"Inbox", "Drafts", "Sent"}};

Every field, in the order a designated initializer names them:

ui::ListView{
    .header, .items, .key, .selected_index, .selected_indices, .item_height, .item_transitions,
    .selection_mode, .item_click_enabled, .item_invoked_enabled, .can_drag_items,
    .can_reorder_items, .allow_drop, .group_by, .sticky_group_headers,
    .items_updating_scroll_mode, .background, .border_brush, .border_thickness,
    .items_panel_vertical_alignment, .item_container_margin,
    .item_content_horizontal_alignment, .style, .margin, .width, .height, .min_width,
    .min_height, .max_width, .max_height, .horizontal_alignment, .vertical_alignment, .visible,
    .tool_tip, .enabled, .accessibility, .on_selection_changed, .on_item_click,
    .on_item_invoked, .on_drag_items_starting, .on_drag_items_completed, .on_items_reordered,
    .on_pointer_entered, .on_pointer_exited, .on_got_focus, .on_lost_focus, .on_key_down,
    .on_wheel, .control, .header_content, .footer_content, .item, .group_header_template,
    .item_context_menu
}

Fields, in declaration order (designators must follow it):

Field Type Default
header ui::Binding<std::string> A plain-text header above the first item, in the group headers' text style. header_content wins over it.
items ui::Binding<std::vector<T>> The collection. A ui::ObservableVector or a ui::Property<std::vector<T>> is followed as it changes. A plain vector or a braced list is copied once.
key ui::ItemKey<std::type_identity_t<T>> What identifies an item when .items is bound to a property: a member pointer or a callable returning the key (see ui::ItemKey). Keys must be unique.
selected_index ui::TwoWayField<std::optional<std::size_t>> The selected item's index. Unset, the default, means nothing is selected, and a bound one can return to unset later (ui::ListViewControl::set_selected_index).
selected_indices ui::Binding<std::vector<std::size_t>> Every selected item, as ascending positions in the collection. Bind a ui::Property<std::vector<std::size_t>> to read and set the selection under Multiple or Extended. Two-way with ui::bind(). Left empty and unbound, .selected_index decides.
item_height ui::Binding<std::optional<float>> A fixed row height in DIPs. Unset (the default) sizes each row to its content, at least 40 DIPs.
item_transitions ui::Binding<ui::TransitionList> Transitions every item container runs (ui::ListViewControl::set_item_transitions()).
selection_mode ui::Binding<ui::SelectionMode> {ui::SelectionMode::Single} None, Single (the default), Multiple (check boxes) or Extended.
item_click_enabled ui::Binding<bool> {false} Whether clicks raise on_item_click, before they select. Setting on_item_click turns it on unless it is bound.
item_invoked_enabled ui::Binding<bool> {false} Whether Enter and double-clicks raise on_item_invoked. Setting on_item_invoked turns it on unless it is bound.
can_drag_items ui::Binding<bool> {false} Whether items can be dragged out; see on_drag_items_starting.
can_reorder_items ui::Binding<bool> {false} Whether dragging within the list moves the item, in the list and in the collection.
allow_drop ui::Binding<bool> {false} Whether the list accepts drops.
group_by ui::Callback<std::string(const std::type_identity_t<T>&)> Files the items under group headers by the key it returns for each item. The groups come in the order their keys first appear, and the items keep their order within a group, without sorting. Asked again on every collection change. Positions in the collection stay the indices.
sticky_group_headers ui::Binding<bool> {true} Whether the current group's header stays pinned at the top while its items scroll under it.
items_updating_scroll_mode ui::Binding<ui::ItemsUpdatingScrollMode> {ui::ItemsUpdatingScrollMode::KeepItemsInView} What stays in view as items arrive. KeepLastItemInView makes a chat-style log follow its end.
background ui::Binding<ui::Paint> Background behind the rows; nullopt draws none.
border_brush ui::Binding<ui::Paint> The border color: a fixed color or a theme color.
border_thickness ui::Binding<ui::Thickness> The border thickness, DIPs; zero (the default) draws no border.
items_panel_vertical_alignment ui::Binding<ui::Alignment> {ui::Alignment::Stretch} Where the rows sit vertically when they do not fill the list (Trailing: a short list sits at the bottom).
item_container_margin ui::Binding<ui::Thickness> Every item container's margin, and its content's horizontal alignment (item_content_horizontal_alignment).
item_content_horizontal_alignment ui::Binding<ui::Alignment> {ui::Alignment::Stretch} See item_container_margin.
on_selection_changed ui::Callback<void(ui::SelectedItem<std::type_identity_t<T>>)> Called with a copy of the first selected item, unset when nothing is selected (see ui::SelectedItem). Fires for user and programmatic changes, and only when the selection changed. The copy is safe to keep.
on_item_click ui::Callback<void(const std::type_identity_t<T>&)> Called with the clicked item, before the click selects. Turns item_click_enabled on. The reference is valid during the call. An item of a ui::ObservableVector stays at its address while it is in the collection.
on_item_invoked ui::Callback<void(const std::type_identity_t<T>&)> Called with the item on Enter or a double-click. With SelectionMode::None a click or Space also invokes. Turns item_invoked_enabled on. The reference is valid as for on_item_click.
on_drag_items_starting ui::Callback<ui::Decision(const ui::DragItemsStartingArgs&)> A drag of items starts. Fill in the drag package, or cancel. The args name the items by position.
on_drag_items_completed ui::Callback<void(const ui::DragItemsCompletedArgs&)> A drag of items ended, with the positions of the dragged items and what the drop did (None when refused).
on_items_reordered ui::Callback<void(const ui::ItemsReorderedArgs&)> A reorder moved the item at position from to to. The collection has already been moved.
on_pointer_entered ui::Action The pointer moved onto the control.
on_pointer_exited ui::Action The pointer left the control.
on_got_focus ui::Action Keyboard focus arrived on the control, or on anything inside it, from outside it. A control with nothing focusable in it never gets focus, and never raises this.
on_lost_focus ui::Action Keyboard focus left the control and everything inside it.
on_key_down ui::Callback<ui::Handling(const ui::KeyEvent&)> A key pressed while focus is on the control or inside it, before the control's own handling; ui::Handled stops it there (Element::on_key_down()).
on_wheel ui::Callback<ui::Handling(const ui::PointerEvent&)> A wheel turn over the control, before the control scrolls; ui::Handled stops it there (Element::on_wheel()).
control ui::ControlOut<std::type_identity_t<ui::ListView>> Out: the handle that receives the built control. Its type uses std::type_identity_t, so passing a handle does not take part in deducing the type argument.
header_content ui::Spec Content above the first row (header_content) and after the last (footer_content). Both scroll with the rows.
footer_content ui::Spec See header_content.
item ui::Callback<ui::Spec(std::type_identity_t<T>&)> How one row looks: a callable taking const T&, or T& to edit an item of a ui::ObservableVector. Left out, an item that is text is shown as that text. Any other item type needs one, and a debug build asserts without it.
group_header_template ui::Callback<ui::Spec(const std::string& key)> A group header's content, given the group's key. Left out, the header shows the key as text. Needs .group_by.
item_context_menu ui::Callback<std::vector<ui::MenuItem>(const std::type_identity_t<T>&)> The context menu of an item: a callable taking the item and returning the menu's entries. Called on a right-click on the item, or on Shift+F10 or the Menu key while it is current. The item is selected first. An empty list opens no menu. An item of a ui::ObservableVector stays where it is while it is in the collection, so an entry's on_click may keep a reference to it. Any other item is valid during the call only, so capture it by value.

Plus the common fields style, margin, width, height, min_width, min_height, max_width, max_height, horizontal_alignment, vertical_alignment, visible, tool_tip, enabled, accessibility every spec carries (conventions).

build

std::unique_ptr<Element> build()&&

Consumes the spec and returns the built ui::ListViewControl; the caller owns it.

ui::ListViewControl

Derives from ui::ListViewBase.

A vertical, virtualized, selectable list of templated items.

Each item sits in a container with rounded chrome: a backplate for hover and press, a 3 pixel accent selection indicator in Single and Extended mode, and a check box per item in Multiple mode. Items, templates, selection, groups, header and footer, drag reordering and scrolling come from ListViewBase.

Example:

// Declarative: the items and how one row looks (plain strings need no `.item`).
std::vector<std::string> fruits{"Apple", "Banana", "Cherry"};
ui::Control<ui::ListView<std::string>> list;
window.set_content(ui::ListView<std::string>{
    .items                = fruits,
    .selection_mode       = ui::SelectionMode::Single,
    .height               = 240.0f,
    .on_selection_changed = [&](std::optional<std::string> fruit)
    { std::println("selected {}", fruit ? *fruit : "nothing"); },
    .control              = list,
    .item                 = [](const std::string& fruit) -> ui::Spec {
        return ui::Text{.text = fruit};
    },
});

// Imperative:
auto stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto names = std::make_unique<ui::ListViewControl>();
names->set_items({"One", "Two", "Three"});
names->set_item_click_enabled(true);
names->on_item_click([](std::size_t i) { std::println("clicked {}", i); });
stack->add(std::move(names));

ListViewControl

ListViewControl()

An empty list in Single selection mode.

Tests

  • tests/test_list_view.cpp