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 .itemmay take the item asT&. - 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::ObservableVectorui::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¶
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¶
An empty list in Single selection mode.
Tests¶
tests/test_list_view.cpp