
# PromptPlus
## **TreeSelect
— Operations**
[](https://www.nuget.org/packages/PromptPlus)
[](https://opensource.org/licenses/MIT)
[](https://dotnet.microsoft.com/)
</div>
[← Back to Home](../../../README.md) • **Next:** [TreeSelect — Styles →](/PromptPlus/controls/treeselect/styles.html)
---
How the `TreeSelect` control is built and how it behaves while running: the tree model, keyboard,
expand/collapse, filtering, leaf-only rules, validation, history, and view-only mode.
---
## Anatomy of the control
```
Pick a service: API (service) ← prompt + live answer (follows the cursor) + ExtraInfo
Type to filter the full path ← description (optional / dynamic)
▼ Company ← root (expanded container)
▼ Engineering (dept) ← container + ExtraInfo
▶ Backend (team) ← collapsed container
› API (service) ← focused leaf
▶ Sales (dept)
Filter: ap_ ← live filter text (when filtering)
Page 1/2 ← pagination
Enter: confirm Esc: cancel ← tooltip
```
The answer line updates as you navigate and includes `ExtraInfo`/`ExtraInfoAsync` when set (same
two-space format as the list row), scrollable via `Home`/`End`/`←`/`→` when it overflows the width.
Once confirmed (**Enter**), the final answer shown is the plain node text/path — no `ExtraInfo`.
Every region can be recolored — see [Styles](/PromptPlus/controls/treeselect/styles.html).
---
## Building the tree model
The hierarchy is built explicitly, in code, before `Run`. Three calls are **required**:
[`Root`](/PromptPlus/controls/treeselect/methods.html#root), [`TextSelector`](/PromptPlus/controls/treeselect/methods.html#textselector), and
[`DefaultMatchBy`](/PromptPlus/controls/treeselect/methods.html#defaultmatchby).
1. **Set the root.** `Root(value)` defines the single top-level node. Call it first — adding children
before the root throws `InvalidOperationException`.
2. **Add first-level nodes.** [`AddLast`](/PromptPlus/controls/treeselect/methods.html#addlast) / [`AddFirst`](/PromptPlus/controls/treeselect/methods.html#addfirst)
attach a node under the root and **return an [`ITreeNode`](/PromptPlus/controls/treeselect/methods.html#itreenodet)**.
3. **Add children.** Call `AddLast` / `AddFirst` on the returned node to nest deeper — repeat to any
depth.
4. **Order siblings** with [`AddAfter`](/PromptPlus/controls/treeselect/methods.html#addafter) / [`AddBefore`](/PromptPlus/controls/treeselect/methods.html#addbefore).
```csharp
var tree = PromptPlus.Controls.TreeSelect("Pick an item")
.Root(company)
.TextSelector(n => n.Name)
.DefaultMatchBy((a, b) => a.Id == b.Id);
var eng = tree.AddLast(engineering); // first-level
var backend = eng.AddLast(backendTeam); // child of Engineering
backend.AddLast(api); // leaf
backend.AddLast(database); // leaf
tree.AddLast(sales);
```
- **Container vs leaf is inferred:** a node with children is a container (expandable); a node with
none is a leaf.
- **[`Interaction` / `InteractionAsync`](/PromptPlus/controls/treeselect/methods.html#interaction)** build the same structure from an
external source — you receive the control in the callback and call `AddLast` on it per item.
- **Lazy rendering:** visible rows are materialized on expand and released on collapse, so memory
stays proportional to what is on screen — even for trees with thousands of nodes.
---
## Keyboard
| Key | Action |
|---|---|
| `↑` / `↓` | Move focus up / down |
| `+` (incl. Numpad `+`) | Expand the focused container |
| `-` (incl. Numpad `-`) | Collapse the focused container |
| `Tab` | Expand and drill into the focused container's children; on a leaf, moves to the next item |
| `Shift+Tab` | Climb back out to (and collapse) the parent when focused on its first child; otherwise moves to the previous item |
| `Page Up` / `Page Down` | Jump one page |
| `Ctrl+Home` / `Ctrl+End` | First / last visible row |
| `Shift+F3` | Toggle short name ↔ full path display |
| `Enter` | Confirm the focused node (runs leaf-only + validation) |
| `Esc` | Abort → `IsAborted == true` |
| Any printable character | Type to filter (when [`Filter`](/PromptPlus/controls/treeselect/methods.html#filter) is not `Disabled`) |
| `Backspace` | Edit / clear the filter text |
| `Home` / `End` / `←` / `→` | Scroll the answer line horizontally (when it overflows the width) |
| `F1` | Cycle tooltip content |
| `Ctrl+F1` | Show / hide the tooltip |
---
## Expand & collapse
- Containers start collapsed unless a [`Default`](/PromptPlus/controls/treeselect/methods.html#default) (or restored history value)
lives inside them — the tree auto-expands the branch down to that node.
- Expanding materializes the children of that node only; collapsing releases them again.
- Leaves have no expand indicator and ignore the expand/collapse keys.
- `Tab` is a shortcut that expands (if needed) and drills straight into a container's children in one
press; `Shift+Tab` climbs back out to the parent (collapsing it) when you're on its first child.
> ⚠️ The arrow keys do **not** expand or collapse — only `+`/`-` (and their Numpad equivalents) do.
> `←`/`→` are reserved for scrolling the answer line (see [Keyboard](#keyboard)).
---
## Filtering
When [`Filter`](/PromptPlus/controls/treeselect/methods.html#filter) is `Contains` or `StartsWith`, typing a printable character
switches the tree into filter mode:
- The whole tree is flattened once and each node's **full path** (parent chain joined by
[`PathSeparator`](/PromptPlus/controls/treeselect/methods.html#pathseparator)) is matched against the typed text.
- Matching is case-insensitive.
- **Backspace** edits the filter; clearing it entirely restores the lazy tree view with the previous
expand/collapse state intact.
`Disabled` (the default) turns typing off entirely — navigation keys only.
---
## Node text & extra info
- [`TextSelector`](/PromptPlus/controls/treeselect/methods.html#textselector) decides each node's label (required).
- [`ExtraInfo` / `ExtraInfoAsync`](/PromptPlus/controls/treeselect/methods.html#extrainfo) render a secondary column next to the label
(for example the node's kind or count).
- [`ShowFullPath`](/PromptPlus/controls/treeselect/methods.html#showfullpath) makes the answer line show the full parent chain instead
of only the node name; `Shift+F3` toggles the same short/long display while navigating.
---
## Leaf-only & validation flow
Pressing **Enter** on the focused node:
1. **Leaf-only gate** — if [`SelectLeafOnly`](/PromptPlus/controls/treeselect/methods.html#selectleafonly) is on and the node is a
container, confirmation is rejected and the tree stays open.
2. **Validation** — [`PredicateSelected`](/PromptPlus/controls/treeselect/methods.html#predicateselected) /
[`PredicateSelectedAsync`](/PromptPlus/controls/treeselect/methods.html#predicateselectedasync), if configured.
3. **Valid** → the control closes and returns the node value.
**Invalid** → the tree stays open and shows the error line.
Use `SelectLeafOnly` for *structural* rules ("pick an actual item, not a folder") and
`PredicateSelected` for *business* rules ("only service items can be chosen").
---
## Initial selection & history
- [`Default(value)`](/PromptPlus/controls/treeselect/methods.html#default) pre-selects a node and expands the tree to reveal it;
provide [`DefaultMatchBy`](/PromptPlus/controls/treeselect/methods.html#defaultmatchby) so the right node is located (required).
- With [`EnableHistory`](/PromptPlus/controls/treeselect/methods.html#enablehistory), the confirmed value is serialized to disk; on
the next run the tree is searched (via `DefaultMatchBy`) for the restored value and pre-selects it
when `Default(..., useDefaultHistory: true)` is in effect.
---
## View-only mode
[`ViewOnly()`](/PromptPlus/controls/treeselect/methods.html#viewonly) renders the tree for display only:
- Navigation and expand/collapse (`+`/`-`) still work, but nodes cannot be confirmed as a choice.
- Enter always returns the node the tree started on: the [`Default`](/PromptPlus/controls/treeselect/methods.html#default) target if
one was set, otherwise the **root node** — never `null`, since a root is mandatory. Wherever you
navigated to is ignored.
- Useful for showing a read-only snapshot of a hierarchy inline with other prompts.
---
## Options that change behavior
Set per instance via [`Options(...)`](/PromptPlus/controls/treeselect/methods.html#options), or globally on
[`PromptPlus.Config`](/PromptPlus/global-behaviors.html):
| Option | Effect on `TreeSelect` |
|---|---|
| `EnabledAbortKey(false)` | Removes Esc — the user must choose |
| `HideAfterFinish(true)` | Erases the tree after confirm — the whole control is erased, not just the interactive part |
| `ShowTooltip(false)` | Hides the keyboard hint line |
| `Prompt(...)` / `Description(...)` | Overrides the prompt / description text |
`PageSize` can be set per control ([`PageSize`](/PromptPlus/controls/treeselect/methods.html#pagesize)) or globally
(`PromptPlus.Config.PageSize`).
---
## Edge cases & gotchas
- **The result is nullable.** `Run` returns `ResultPrompt<T?>`; `.Content` is `null`/`default` only
when aborted. `ViewOnly` with no `Default` still returns a real value — the root node — not `null`.
Always branch on `IsAborted`.
- **Custom types need equality.** [`DefaultMatchBy`](/PromptPlus/controls/treeselect/methods.html#defaultmatchby) is required and drives
both `Default` and history lookups — without correct equality the intended node may not be found.
- **Root must come first.** Adding nodes before `Root` throws `InvalidOperationException`.
- **Async callbacks block the UI thread** — keep validators, extra-info, and description callbacks fast.
---
## See also
- [Methods](/PromptPlus/controls/treeselect/methods.html) — the API these behaviors come from
- [Keyboard Bindings](/PromptPlus/keyboard-bindings.html) — full physical-key reference
- [Global Behaviors](/PromptPlus/global-behaviors.html) — the config layer behind `Options`
- [TreeMultiSelect → Operations](/PromptPlus/controls/treemultiselect/operations.html) — the multiple-choice sibling