PromptPlus

PromptPlus # PromptPlus ## **TreeMultiSelect — Operations** [![NuGet](https://img.shields.io/badge/NuGet-PromptPlus-blue)](https://www.nuget.org/packages/PromptPlus) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![.NET](https://img.shields.io/badge/.NET-8%20%7C%209%20%7C%2010-512BD4)](https://dotnet.microsoft.com/) </div> [← Back to Home](../../../README.md) • **Next:** [TreeMultiSelect — Styles →](/PromptPlus/controls/treemultiselect/styles.html) --- How the `TreeMultiSelect` control is built and how it behaves while running: the tree model, keyboard, checking and cascade, filtering, leaf-only rules, validation, ranges, history, and view-only mode. --- ## Anatomy of the control ``` Check items and press Enter: API (service) ← prompt + live answer (follows the cursor) + ExtraInfo Space=check Enter=confirm ESC=abort ← description (optional / dynamic) ▼ [~] Company ← root, indeterminate (some descendants checked) ▼ [x] Engineering (dept) ← checked container + ExtraInfo ▶ [x] Backend (team) ← collapsed, fully checked › [ ] API (service) ← focused unchecked leaf ▶ [ ] Sales (dept) Filter: ap_ ← live filter text (when filtering) Checked: 3 ← tagged count Page 1/2 ← pagination Enter: confirm Esc: cancel ← tooltip ``` Checkbox states: `[ ]` unchecked · `[x]` checked · `[~]` indeterminate (some but not all descendants checked). 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 checked-values summary — no `ExtraInfo`. Every region can be recolored — see [Styles](/PromptPlus/controls/treemultiselect/styles.html). --- ## Building the tree model The hierarchy is built explicitly, in code, before `Run` — identical to [`TreeSelect`](/PromptPlus/controls/treeselect/operations.html#building-the-tree-model). Three calls are **required**: [`Root`](/PromptPlus/controls/treemultiselect/methods.html#root), [`TextSelector`](/PromptPlus/controls/treemultiselect/methods.html#textselector), and [`DefaultMatchBy`](/PromptPlus/controls/treemultiselect/methods.html#defaultmatchby). 1. **Set the root.** `Root(value)` defines the single top-level node. Call it first. 2. **Add first-level nodes.** [`AddLast`](/PromptPlus/controls/treemultiselect/methods.html#addlast) / [`AddFirst`](/PromptPlus/controls/treemultiselect/methods.html#addfirst) attach a node under the root and **return an [`ITreeNode`](/PromptPlus/controls/treemultiselect/methods.html#itreenodet)**. 3. **Add children.** Call `AddLast` / `AddFirst` on the returned node to nest deeper. 4. **Order siblings** with [`AddAfter`](/PromptPlus/controls/treemultiselect/methods.html#addafter) / [`AddBefore`](/PromptPlus/controls/treemultiselect/methods.html#addbefore). ```csharp var tree = PromptPlus.Controls.TreeMultiSelect("Check items") .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 (with a tri-state checkbox); a node with none is a leaf. - **[`Interaction` / `InteractionAsync`](/PromptPlus/controls/treemultiselect/methods.html#interaction)** build the same structure from an external source. - **Lazy rendering:** rows are materialized on expand and released on collapse, so large trees stay cheap. --- ## 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 | | `Space` | Check / uncheck the focused node (cascades per [`CascadeCheck`](/PromptPlus/controls/treemultiselect/methods.html#cascadecheck)) | | `Ctrl+Space` | Recursive check of a container + descendants (when [`RecursiveMarkWithCtrlSpace`](/PromptPlus/controls/treemultiselect/methods.html#recursivemarkwithctrlspace) is on) | | `F2` | Toggle-all (check / uncheck every node) | | `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 checked set (runs the range + validation gates) | | `Esc` | Abort → `IsAborted == true` | | Any printable character | Type to filter (when [`Filter`](/PromptPlus/controls/treemultiselect/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 | --- ## Checking & cascade The check key (**Space**) toggles the focused node. What happens next depends on two settings: - **[`CascadeCheck`](/PromptPlus/controls/treemultiselect/methods.html#cascadecheck)** (default `true`): checking/unchecking a container propagates the state to all its descendants. With `false`, only the container itself toggles. - **[`RecursiveMarkWithCtrlSpace`](/PromptPlus/controls/treemultiselect/methods.html#recursivemarkwithctrlspace)** (default `false`): - `false` → plain **Space** performs the recursive mark on containers (when `CascadeCheck` is on). - `true` → plain **Space** toggles only the focused node; **Ctrl+Space** does the recursive mark. Containers show a **tri-state** checkbox: unchecked when no descendant is checked, checked when all are, and indeterminate (`[~]`) when only some are. `F2` toggles every node at once. > ⚠️ The arrow keys do **not** expand or collapse — only `+`/`-` (and their Numpad equivalents) do. > `←`/`→` are reserved for scrolling the answer line (see [Keyboard](#keyboard)). `Tab`/`Shift+Tab` > are a separate drill-in/climb-out shortcut that also expands/collapses as a side effect. --- ## Leaf-only checking [`CheckLeafOnly()`](/PromptPlus/controls/treemultiselect/methods.html#checkleafonly) blocks checking of container nodes — only leaves can be checked. Attempting to check a container is rejected. Use it when only concrete items (not folders) are valid selections. --- ## Filtering When [`Filter`](/PromptPlus/controls/treemultiselect/methods.html#filter) is `Contains` or `StartsWith`, typing a printable character switches the tree into filter mode: nodes are matched against their **full path** (parent chain joined by [`PathSeparator`](/PromptPlus/controls/treemultiselect/methods.html#pathseparator)), case-insensitively. Checking still works on matched nodes. **Backspace** edits the filter; clearing it restores the tree view. `Disabled` (the default) turns typing off entirely. --- ## Node text & extra info - [`TextSelector`](/PromptPlus/controls/treemultiselect/methods.html#textselector) decides each node's label (required). - [`ExtraInfo` / `ExtraInfoAsync`](/PromptPlus/controls/treemultiselect/methods.html#extrainfo) render a secondary column next to the label. - [`ShowFullPath`](/PromptPlus/controls/treemultiselect/methods.html#showfullpath) makes the answer line show the full parent chain for each checked item; `Shift+F3` toggles the same short/long display while navigating. --- ## Disabled nodes Any node added with `disable: true` (on [`Root`](/PromptPlus/controls/treemultiselect/methods.html#root), [`AddLast`](/PromptPlus/controls/treemultiselect/methods.html#addlast), [`AddFirst`](/PromptPlus/controls/treemultiselect/methods.html#addfirst), [`AddAfter`](/PromptPlus/controls/treemultiselect/methods.html#addafter), [`AddBefore`](/PromptPlus/controls/treemultiselect/methods.html#addbefore), or the chained [`ITreeMultiSelectNode`](/PromptPlus/controls/treemultiselect/methods.html#imultitreenodet) overloads) is rendered with [`TreeMultiSelectStyles.Disabled`](/PromptPlus/controls/treemultiselect/styles.html) and follows a distinct tri-state / cascade semantic: 1. **Interactive checks are blocked.** `Space` / `Ctrl+Space` on a disabled node do nothing (`SelectionDisabled`), just like the single-selection `TreeSelect`. 2. **Cascade passes through, it does not mark.** A cascade (`Ctrl+Space` on an ancestor, with `CascadeCheck` on) crosses a disabled container to reach its *enabled* descendants without touching the disabled container's own flag. 3. **`Default(...)` can force it.** A disabled node can still be force-checked through [`Default`](/PromptPlus/controls/treemultiselect/methods.html#default) (or the construction-time `check: true`), bypassing the interactive block. 4. **Forced marks survive clear-all.** `F2` (toggle-all) skips disabled nodes in *both* directions: it neither checks them on select-all nor unchecks a force-checked disabled node on clear-all. > Because a container's checkbox under `CascadeCheck` is always derived from its enabled leaf descendants, > a disabled container whose descendants are fully checked reports **Indeterminate** (never claims a > confirmation of its own) unless its own flag was explicitly forced via `Default`/`check`. --- ## Confirmation flow: range & validation Pressing **Enter** confirms the checked set after two gates: 1. **Range gate** — if [`Range(min, max)`](/PromptPlus/controls/treemultiselect/methods.html#range) is set, confirmation is blocked until the number of checked items is within `[min, max]`. 2. **Per-node validation** — [`PredicateChecked`](/PromptPlus/controls/treemultiselect/methods.html#predicatechecked) / [`PredicateCheckedAsync`](/PromptPlus/controls/treemultiselect/methods.html#predicatecheckedasync) run when the user tries to *check* a node; failing nodes show an error and cannot be checked. When both pass, the control closes and returns the checked values as `T[]`. --- ## Initial values & history - [`Default(values)`](/PromptPlus/controls/treemultiselect/methods.html#default) pre-checks nodes and expands the tree to reveal each one; provide [`DefaultMatchBy`](/PromptPlus/controls/treemultiselect/methods.html#defaultmatchby) so the right nodes are located (required). - With [`EnableHistory`](/PromptPlus/controls/treemultiselect/methods.html#enablehistory), the checked set is persisted to disk and restored on the next run; the restored values override `Default` when `useDefaultHistory: true` is in effect. --- ## View-only mode [`ViewOnly()`](/PromptPlus/controls/treemultiselect/methods.html#viewonly) renders the tree for display only: - Navigation and expand/collapse (`+`/`-`) still work, but Space is disabled — nothing can be checked or unchecked. - Enter returns the pre-checked [`Default`](/PromptPlus/controls/treemultiselect/methods.html#default) values. - Useful for showing a read-only snapshot of a checked hierarchy inline with other prompts. --- ## Options that change behavior Set per instance via [`Options(...)`](/PromptPlus/controls/treemultiselect/methods.html#options), or globally on [`PromptPlus.Config`](/PromptPlus/global-behaviors.html): | Option | Effect on `TreeMultiSelect` | |---|---| | `EnabledAbortKey(false)` | Removes Esc — the user must confirm | | `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/treemultiselect/methods.html#pagesize)) or globally (`PromptPlus.Config.PageSize`). --- ## Edge cases & gotchas - **The result is an array.** `Run` returns `ResultPrompt<T[]>`; `.Content` is an empty array when nothing is checked. Always branch on `IsAborted` first. - **Custom types need equality.** [`DefaultMatchBy`](/PromptPlus/controls/treemultiselect/methods.html#defaultmatchby) is required and drives `Default`, history, and cascade matching. - **Range blocks Enter, predicate blocks Space.** `Range` gates the final confirm; `PredicateChecked` gates individual checks — they are separate stages. - **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/treemultiselect/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` - [TreeSelect → Operations](/PromptPlus/controls/treeselect/operations.html) — the single-choice sibling