TreeView¶
Header: winui/ui/collections/TreeView.hpp. Spec: winui/ui/collections/TreeView.hpp.
A hierarchy of expandable rows, with single or multiple (check box) selection, templated rows, and rearranging by drag and drop.
The nodes are plain TreeNode data, set with set_roots(). The rows are the visible nodes in order, and the tree draws each row's chrome itself: backplate, selection indicator, indentation and chevron. Which nodes are expanded and selected is kept by each node's key, so set_roots() with a changed tree keeps them. The tree is a single Tab stop. Up and Down move between rows. Left and Right collapse, expand, or step to the parent or the first child. Enter invokes. With set_can_drag_items() and set_allow_drop() on, a row can be dragged onto another row to become its last child, or into the gap between rows to reorder. on_drag_items_completed() reports the move. The tree is not virtualized. Every visible row is realized, so keep very large trees collapsed.
Example:
auto tree = std::make_unique<ui::TreeViewControl>();
tree->set_roots({{.text = "Documents", .children = {{.text = "Report.docx"}}, .expanded = true},
{.text = "Network", .has_children = true}});
tree->on_expanding([raw = tree.get()](const ui::TreeNode& node) {
if (node.text == "Network" && node.children.empty())
{
std::vector<ui::TreeNode> roots = raw->roots();
roots[1].children.push_back({.text = "Server01"}); // load lazily
raw->set_roots(std::move(roots)); // the node goes on expanding
}
});
// Enter or double-click:
tree->on_item_invoked([](const ui::TreeNode& node) { std::println("open {}", node.text); });
ui::TreeView¶
A hierarchy of expandable rows over the app's data. Builds a ui::TreeViewControl.
.items are the top-level items, and .children says where an item's children are: a member (&Folder::subfolders) or a function returning them. The data is the model. The tree shows it and keeps only which rows are expanded and selected, by .key, so that state survives a changed collection, such as a reload, a rename or children loaded on expand. A function .children can make an item's children as they are needed, and .has_children shows the chevron before they exist. on_expanding loads them into the bound property, and the row opens.
With a member .children and a bound ui::Property, a drag (with can_drag_items and allow_drop) writes the rearranged items back.
The ready-made item is ui::TreeNode, which needs no .children, .key or .item. An item type of its own needs .item, the row's content. Name the item type as for the other views, ui::TreeView<Folder>. A braced list of ui::TreeNode works with a plain ui::TreeView.
Example:
struct Folder
{
std::string path;
std::string name;
bool has_subfolders = false;
std::vector<Folder> subfolders;
friend bool operator==(const Folder&, const Folder&) = default;
};
ui::Property<std::vector<Folder>> folders{{{"C:/", "Local Disk (C:)", true}, {"D:/", "Data (D:)", true}}};
window.set_content(ui::TreeView<Folder>{
.items = folders,
.children = &Folder::subfolders,
.key = &Folder::path,
.has_children = &Folder::has_subfolders,
.on_expanding = [&](const Folder& f) {
std::vector<Folder> drives = folders.get(); // load the drive's folders lazily
for (Folder& drive : drives)
if (drive.path == f.path && drive.subfolders.empty())
drive.subfolders = {{f.path + "Users", "Users"}, {f.path + "Windows", "Windows"}};
folders = drives;
},
.item = [](const Folder& f) -> ui::Spec { return ui::Text{.text = f.name}; },
});
Every field, in the order a designated initializer names them:
ui::TreeView{
.items, .children, .key, .has_children, .expanded, .indent_width, .selection_mode,
.can_drag_items, .allow_drop, .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_expanding, .on_collapsed, .on_drag_items_completed, .on_pointer_entered,
.on_pointer_exited, .on_got_focus, .on_lost_focus, .on_key_down, .on_wheel, .control,
.item, .item_context_menu
}
Fields, in declaration order (designators must follow it):
| Field | Type | Default | |
|---|---|---|---|
items |
ui::Binding<std::vector<T>> |
The top-level items: a ui::Property<std::vector<T>> the tree follows, or a plain vector. |
|
children |
ui::detail::TreeChildren<std::type_identity_t<T>> |
Where an item's children are: a member pointer, or a function returning them. Not needed for ui::TreeNode. | |
key |
ui::detail::TreeKey<std::type_identity_t<T>> |
What identifies an item: a member pointer or a function giving a string or a number. Keys must be unique in the tree. Left out, an item is known by its position. | |
has_children |
std::function<bool(const std::type_identity_t<T>&)> |
Whether an item shows the chevron before its children exist. Load them in on_expanding. |
|
expanded |
std::function<bool(const std::type_identity_t<T>&)> |
Whether an item's children show when the tree first sees its key. After that the user's expanding and collapsing count. Left out, every item starts collapsed. | |
indent_width |
ui::Binding<float> |
{16.0f} |
Indentation per nesting level, DIPs. |
selection_mode |
ui::Binding<ui::TreeViewSelectionMode> |
{ui::TreeViewSelectionMode::Single} |
None, Single (the default) or Multiple (check boxes). |
can_drag_items |
ui::Binding<bool> |
{false} |
Whether items can be dragged to another place in the tree; off by default. |
allow_drop |
ui::Binding<bool> |
{false} |
Whether the tree accepts drops; off by default. |
on_selection_changed |
ui::Callback<void(ui::SelectedItem<std::type_identity_t<T>>)> |
The selection changed, by a click, the arrow keys, a check box, or a selected item gone from .items. Called with a copy of the selected item, the first one in Multiple mode, or unset when nothing is selected (see ui::SelectedItem). |
|
on_item_click |
ui::Callback<void(const std::type_identity_t<T>&)> |
A row was clicked, before the click selects it. | |
on_item_invoked |
ui::Callback<void(const std::type_identity_t<T>&)> |
A row was opened by Enter or a double-click, and under TreeViewSelectionMode::None also by a single click. | |
on_expanding |
ui::Callback<void(const std::type_identity_t<T>&)> |
An item is opening. Load its children into the bound property here. | |
on_collapsed |
ui::Callback<void(const std::type_identity_t<T>&)> |
An item has closed. | |
on_drag_items_completed |
ui::Callback<void(const ui::TreeViewMovedArgs<std::type_identity_t<T>>&)> |
A drag moved an item. Called with the item, its new parent and its position there. | |
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<ui::TreeView> |
Out: the handle that receives the built control. | |
item |
ui::Callback<ui::Spec(const std::type_identity_t<T>&)> |
A row's content for an item. Left out, a ui::TreeNode row shows its text. | |
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 its row, or on Shift+F10 or the Menu key while it is current. The item is selected first. An empty list opens no menu. The reference is valid during the call only, so an entry's on_click captures what it needs 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::TreeViewControl; the caller owns it.
ui::TreeViewControl¶
Derives from ui::Element, ui::ValueProvider.
A hierarchy of expandable rows, with single or multiple (check box) selection, templated rows, and rearranging by drag and drop.
The nodes are plain TreeNode data, set with set_roots(). The rows are the visible nodes in order, and the tree draws each row's chrome itself: backplate, selection indicator, indentation and chevron. Which nodes are expanded and selected is kept by each node's key, so set_roots() with a changed tree keeps them. The tree is a single Tab stop. Up and Down move between rows. Left and Right collapse, expand, or step to the parent or the first child. Enter invokes. With set_can_drag_items() and set_allow_drop() on, a row can be dragged onto another row to become its last child, or into the gap between rows to reorder. on_drag_items_completed() reports the move. The tree is not virtualized. Every visible row is realized, so keep very large trees collapsed.
Example:
auto tree = std::make_unique<ui::TreeViewControl>();
tree->set_roots({{.text = "Documents", .children = {{.text = "Report.docx"}}, .expanded = true},
{.text = "Network", .has_children = true}});
tree->on_expanding([raw = tree.get()](const ui::TreeNode& node) {
if (node.text == "Network" && node.children.empty())
{
std::vector<ui::TreeNode> roots = raw->roots();
roots[1].children.push_back({.text = "Server01"}); // load lazily
raw->set_roots(std::move(roots)); // the node goes on expanding
}
});
// Enter or double-click:
tree->on_item_invoked([](const ui::TreeNode& node) { std::println("open {}", node.text); });
ItemTemplate¶
Makes a row's content for a node and may choose a different look per node. Called during measure for each visible row. The result is kept per node until the tree changes.
TreeViewControl¶
An empty, enabled tree in Single selection mode, with dragging and dropping off.
set_roots¶
Replaces the tree. Nodes whose key was there before stay expanded and selected as they were. A new key takes its node's expanded. A selected node whose key is gone is deselected, which raises on_selection_changed(). Safe to call from on_expanding(), which is how children added on expand arrive. Invalidates every TreeNode* into the old tree and every row content the template made, and lays the tree out again.
roots¶
The top-level nodes, each owning its subtree, as last set or rearranged by a drag.
find¶
The node with key, or nullptr. A node without a key is found by its path of texts.
key_of¶
A node's effective key: its key, or the path of texts from its root.
set_item_template¶
Sets the row content template. nullptr, the default, shows each node's text. Rebuilds every row's content on the next layout.
is_expanded¶
Whether node's children show.
set_expanded¶
Expands or collapses the node with key, as the chevron does, raising on_expanding() first.
selection_mode¶
The selection mode; Single by default.
set_selection_mode¶
Switches the mode and adapts the selection. None clears it, Single keeps only the first selected node, and Multiple shows the check boxes. Lays the tree out again.
selected¶
The selected node, the first one in Multiple mode, or nullptr.
selected_nodes¶
Every fully selected node, in tree order.
select¶
Selects the node with key alone, in Single mode. An unknown key clears the selection.
selection_state¶
A node's state in Multiple mode. A leaf's is whether it is selected. A parent's follows its children: Selected when all are, Unselected when none are, else Partial.
select_all¶
Selects every node in Multiple mode, and does nothing in the other modes. Fires on_selection_changed() once.
on_selection_changed¶
Fired whenever the selection changes, by a click, the arrow keys, a check box, or a selected key gone from set_roots(). Called with selected(): the selected node, the first one in Multiple mode, or nullptr when nothing is selected. Replaces any previous handler.
on_item_click¶
Called with the node on a single click on its row, before the click selects it. Not fired for a click on the chevron or check box.
on_item_invoked¶
Called when a row is opened: Enter or a double-click, and under TreeViewSelectionMode::None also a single click. Raised after the click selects. Not fired for a click on the chevron or check box.
on_expanding¶
A node is opening. Add the children of a node with has_children here, by setting a tree that has them (set_roots()). Fires before the node expands. If the node still has no children and no has_children afterwards, it stays collapsed.
on_collapsed¶
Fired after a node has closed.
can_drag_items¶
Whether rows can be dragged out to rearrange the tree; false by default. Element's allow-drop flag, also off by default, decides whether the tree accepts drops. Rearranging the tree needs both.
set_can_drag_items¶
Turns dragging rows on or off. Takes effect on the next press.
on_drag_items_completed¶
Called with the node that moved, at its new place, its new parent (nullptr for a root) and its position there. roots() already holds the rearranged tree.
row_height¶
The row height of the last measure, in DIPs, for callers that compute rows. 0 before the first measure.
indent_width¶
Indentation per nesting level, in DIPs; 16 by default.
set_indent_width¶
Changes the per-level indentation and lays the tree out again.
visible_row_count¶
How many rows are visible, that is not inside a collapsed ancestor.
visible_node_at¶
The node drawn at visible row index, counted from 0 at the top, or nullptr when out of range. Valid until the tree is next rearranged.
focused¶
Whether the tree holds keyboard focus and draws the focus ring.
focusable¶
A Tab stop only while enabled and non-empty.
set_item_context_menu¶
Sets the context menu of a node. It is asked for on a right-click on its row, or on Shift+F10 or the Menu key while the node is the current one. The node is selected first, unless it already is or the selection mode is not Single. An empty list opens no menu. Replaces any previous one.
raise_context_requested¶
Offers a right-click first to the templated content of the row under position, then to the tree's own context flyout, and then opens the node's context menu.
for_each_child¶
The node context menu while it shows, so the window finds its popup.
drag_enter¶
The drop events. drag_over() is asked first on the innermost drop target under the pointer, then on its drop targets up the tree until one accepts; every element asked has heard drag_enter() first and hears drag_leave() once the drag is no longer its business. The one that accepted gets drop(). A control overrides them; the defaults run the handlers set with on_drag_enter()/on_drag_over()/on_drag_leave()/on_drop().
drag_enter(): the drag has come over this element; drag_over() follows at once.
drag_over¶
The drag is over this element: answer what a drop here would do. Asked when it arrives and at every move. The default runs the on_drag_over() handler, and refuses without one.
drag_leave¶
The drag left this element, was canceled over it, or was dropped on another target.
drop¶
The pointer was released over this element while its drag_over() accepted. What the drop did is the operation it accepted.
drag_completed¶
A drag this element started has ended. result is what the target did (None when the drag was canceled or dropped nowhere).
automation_children¶
One accessibility item per root row. Each lists its content and its visible children.
automation_parent_of¶
A row's accessibility item is the parent of its content and of its child rows' items.
automation_focus¶
The row the keyboard is on, so a screen reader reads it as the arrow keys move.
automation_role¶
Reported to screen readers as a Tree.
measure_override¶
Implement these instead of measure() and arrange(). The base class has already applied margin, size limits and alignment by the time these run. measure_override() is pure (every concrete Element must size itself); arrange_override() defaults to a no-op since most leaves need nothing beyond having bounds() set, which the base already did.
arrange_override¶
Position children within the rect arrange() settled on (also in bounds(), window DIPs). Default: nothing, for a leaf.
pointer_override¶
A pointer event for this element: the element under the pointer, one inside it that did not handle the event, or the element holding the capture. The position is in layout coordinates (handle_pointer()). Return true when the event did something here, and it goes no further up; a handled press also captures the pointer until its release. A control that acts on a press (a click, a toggle, a caret placed, a drag started) returns true for the press and its release, so a list row or a card around it does not act on the same click. Default: false.
render_override¶
What the control draws, in layout coordinates; render() has already applied the visual layer. A container draws its children through their render(), never their render_override().
tick_override¶
Ticks the templated row content and drives the hover-to-expand timer during a drag.
clear_dirty_override¶
This element's own part of clear_dirty(): clear any part it owns but leaves out of for_each_child() (a parked page, a hidden button), through that part's clear_dirty(). Default: nothing.
enabled_changed¶
is_enabled() flipped, through set_enabled() here or on an ancestor, or a move to another parent. Drop any press or hover state. The element is redrawn either way. No-op by default.
focus_changed¶
Keyboard focus arrived (true) or left (false); is_focused() already says so. The element is redrawn either way. No-op by default.
key_override¶
A key for this element: the focused one, or one inside it that did not handle it. Return true when the key did something here, and it goes no further; false lets it bubble on to the parent and then to the shortcuts, a dialog's default button and arrow-key focus moves. Default: false.
focus_ring_rect¶
Inside the row the keyboard is on. Around the whole tree when there is none.
focus_ring_corner_radius¶
The ring's corner radius: the theme's control radius by default.
focus_ring_inside¶
Whether the ring goes inside focus_ring_rect() in every era, its outer edge on the rect. False by default, which leaves it to the theme. An item inside a viewport (a list row, a tab, a menu item) returns true, as does a control in a tight spot, so a neighbor or the viewport edge never cuts its ring.
struct TreeNode¶
A node of a tree: plain data, not an Element. The ready-made item of a ui::TreeView, and what a TreeViewControl is given.
A tree keeps which nodes are expanded and selected by key. A new tree with the same keys, after a reload, a rename or children added on expand, keeps them. A node without a key is known by the path of texts from its root ("Documents/Report.docx").
Example:
ui::TreeView{
.items = {
{.text = "Documents", .children = {{.text = "Report.docx"}, {.text = "Budget.xlsx"}}, .expanded = true},
{.text = "Music", .has_children = true}, // children added on expand
},
}
| Field | Type | Default | |
|---|---|---|---|
text |
std::string |
The row's label, and the name screen readers read for the node. Not displayed when an item template is set. | |
key |
std::string |
What the tree knows the node by. Empty uses the path of texts from its root. | |
children |
std::vector<TreeNode> |
The child nodes, in display order, owned by value. | |
expanded |
bool |
false |
Whether the children show when the tree first sees this node's key. After that the user's expanding and collapsing count. |
has_children |
bool |
false |
Whether to show the chevron before the children exist. An on_expanding handler adds them. |
struct TreeViewDragItemsCompletedArgs¶
What a TreeViewControl's on_drag_items_completed gets: the node that moved, its new parent and its position there.
| Field | Type | Default | |
|---|---|---|---|
node |
const TreeNode& |
The moved node, at its new place. | |
new_parent |
const TreeNode* |
nullptr |
Its new parent; nullptr for a root. |
index |
std::size_t |
0 |
Its position among the new parent's children (or the roots). |
struct TreeViewMovedArgs¶
What a ui::TreeView's on_drag_items_completed gets: the item that moved, its new parent (nullptr for a root) and its position among the new parent's children. Valid during the call.
| Field | Type | Default | |
|---|---|---|---|
item |
const T& |
The moved item. | |
new_parent |
const T* |
nullptr |
Its new parent; nullptr for a root. |
index |
std::size_t |
0 |
Its position among the new parent's children (or the roots). |
Enumerations¶
TreeViewSelectionMode¶
How many nodes a TreeView lets the user select.
| Value | |
|---|---|
None |
No selection. Clicks only invoke. |
Single |
One node, selected by a click or the arrow keys. The default. |
Multiple |
Any number, through a check box per row or Space. A click only invokes. |
TreeNodeSelectionState¶
A node's selection state in Multiple mode, as its check box shows it.
| Value | |
|---|---|
Unselected |
Neither the node nor any descendant leaf is selected. |
Partial |
Some of the node's descendants are selected, not all. The check box shows indeterminate. |
Selected |
The node, or every descendant leaf, is selected. |
Tests¶
tests/test_tree_view.cpp