ControlHost class Core
The one input router of a control tree (docs/control-baum-konzept.md): hit-testing over the same bounds the layout set, FOCUS FOLLOWS THE CLICK, exclusive hover with a pointer-left signal, capture from press to release, Tab traversal in tree order with announcement, keys to the focused control, typing to the focused text sink or — the Excel rule — to the first one, and the default focus ring for controls that do not draw their own.
public sealed class ControlHost
- Namespace
- ArionUI.Presentation.Controls
- Package
- ArionUI.Presentation ·
dotnet add package ArionUI.Presentation --prerelease
Constructors
ControlHost
public ControlHost();
Properties
Announce
public Action<string>? Announce { get; set; }The spoken channel — the pane forwards its announcer here once.
AreaInvalidated
public Action<LogRect>? AreaInvalidated { get; set; }Repaint sink for an AREA (absolute coordinates) - set by a host that repaints in parts (a board repaints the bands the area touches). Unset, every area request repaints whole through ControlHost.Invalidated.
CaretRect
public LogRect? CaretRect { get; }The caret of the focused control where it stands now, absolute; null without one.
Clipboard
public IClipboardText? Clipboard { get; set; }The clipboard the tree's text boxes copy to and paste from; null (the default) leaves Ctrl+C, Ctrl+X and Ctrl+V to the host.
DeviceScale
public double DeviceScale { get; set; }Device pixels per logical unit, for layouts that snap to the pixel grid.
Focused
public Control? Focused { get; }
FocusedId
public string? FocusedId { get; }Focused control's Id — the observable seam for tests and peers.
HasTextSink
public bool HasTextSink { get; }Does the tree contain a text sink at all? Panes answer WantsTextInput with this instead of hard-coding it.
Invalidated
public Action? Invalidated { get; set; }Repaint request sink — the pane forwards its Invalidated event here.
IsAttached
public bool IsAttached { get; }Whether a host shows this tree now. While it does, every control of it that is shown - visible up its parents, not hidden by a breakpoint - has had Control.OnAttached; one that leaves the tree or is hidden, or the whole tree when the host lets it go, has Control.OnDetached.
MeasureText
public Func<string, TextStyle, double>? MeasureText { get; set; }The host's text measure, for controls that size themselves by their text (a label in an auto-sized board track). Null leaves them at their own measure or an estimate.
PresentRequested
public Action? PresentRequested { get; set; }Only what is stamped at present changed (a ring, a tile's hover band): a frame, but nothing of the kept picture is repainted.
Root
public Control? Root { get; }The root of the tree, or null before ControlHost.SetRoot.
WantsTick
public bool WantsTick { get; }
WrapsFocus
public bool WrapsFocus { get; set; }True (the default): Tab after the last control starts again at the first - a modal pane keeps the keyboard. False: ControlHost.MoveFocus drops the focus and answers false after the last control, so the host's own tab order carries on.
Methods
AccessibleChildren
public IReadOnlyList<AccessiblePart> AccessibleChildren(string? groupId);The shown parts directly inside the group groupId, in tree order; null asks for the parts at the top of the tree. Empty for a part that is no group.
AccessiblePartOf
public AccessiblePart? AccessiblePartOf(string id);ONE part, read live — what an automation peer asks for every property it is questioned about.
It used to build the WHOLE list for each of those questions, and a peer is asked about ten: name, role, enabled, focused, bounds, offscreen, and the pattern tests. A screen reader stepping through a pane with twenty parts therefore walked the control tree two hundred times for one read, allocating a record per control each time. Now the walk happens once per tree SHAPE and the answer is a lookup.
AccessibleParts
public IReadOnlyList<AccessiblePart> AccessibleParts();The tree's parts as assistive technology sees them, in tree order — what the hosts' automation peers are fed from: every visible focusable control (the tab stops a keyboard user can reach) and every control that is only READ (a label, a named drawing). A part scrolled out of a clipping container is marked AccessiblePart.Offscreen. Every part has an id of its own: a control without one gets it from its position ("text:3", "control:2"), a second control with the same id gets a suffix ("save~2"), stable while the tree keeps its shape.
On a board, a tile is a ControlRole.Group when it has a Control.AccessibleName and holds parts, or when it holds several parts and opens with a text: the text then names the group and is not listed a second time. The parts inside a group name it as their AccessiblePart.ParentId. This list holds the groups and their parts alike; ControlHost.AccessibleChildren walks it as a tree.
Arrange
public void Arrange(LogRect bounds);
CancelPointer
public void CancelPointer();The gesture was cancelled (Esc, capture lost): the captured control lets go without acting - its release lands nowhere.
ClearFocus
public void ClearFocus();No control holds the focus any more (the keyboard left the tree).
CursorAt
public GridCursor CursorAt(LogPoint p);
FindControl
public Control? FindControl(string id);The VISIBLE control with this id, or null — the seam the automation peers act through (Invoke/Toggle/Value on a part). Filters like ControlHost.AccessibleParts on visibility, and the default empty id matches nothing: a hidden part is not an element, so it cannot be acted on either. A part's own id (a suffixed one too) finds the control behind it.
HasNextTabStop
public bool HasNextTabStop(bool reverse);Whether Tab (Shift+Tab: reverse) has a control to go to without wrapping - the question a host asks before it claims the key.
Invalidate
public void Invalidate();
Invalidate
public void Invalidate(LogRect area);Repaint area; the whole tree when the host repaints only whole.
Measure
public LogSize Measure(LogSize available);
MoveFocus
public bool MoveFocus(bool reverse);Tab/Shift+Tab: next/previous focusable control in tree order, wrapping unless ControlHost.WrapsFocus is off. From "no focus" it enters at the first (or last). Announces the arrival. False when there is nothing to move to - with wrapping off also after the last control, whose focus is then dropped.
MoveFocus
public bool MoveFocus(bool reverse, bool wrap);As ControlHost.MoveFocus, wrapping after the last control only with wrap - a host that hands the keyboard on to its own tab order steps without wrapping whatever the tree was made for.
OnKey
public bool OnKey(ArionKey key, ArionModifiers mods);
OnPointerLeft
public void OnPointerLeft();The pointer left the tree: the hovered control loses its hover.
OnPointerMoved
public void OnPointerMoved(LogPoint p, bool buttonDown);
OnPointerPressed
public bool OnPointerPressed(LogPoint p, ArionModifiers mods, int clickCount = 1);Press: hit-test, FOCUS FOLLOWS THE CLICK, capture, forward. This single method is why "click it, then use the arrows" works in every pane forever.
OnPointerReleased
public void OnPointerReleased(LogPoint p);
OnText
public bool OnText(string text);Typing goes to the focused text sink — or, the Excel rule, jumps to the FIRST text control of the tree and types there. False = no sink at all.
OnWheel
public bool OnWheel(LogPoint p, double delta);The wheel goes to the control under the pointer and, when that one does not scroll, on to the controls around it - text inside a scrolling panel must scroll the panel, it cannot scroll itself.
Render
public void Render(IRenderSurface s, ControlFrame frame);
RequestPresent
public void RequestPresent();Asks for a frame that only presents again (ControlHost.PresentRequested).
SetFocus
public bool SetFocus(string id);Put the focus on a specific control (a pane that opens with its search box active). No announcement — this is programmatic, not a user move.
SetRoot
public void SetRoot(Control newRoot);Make newRoot the tree - also to announce that the SAME root changed its shape (controls added or removed): every control is adopted again, the automation index is rebuilt, and a focus, hover or capture on a control that left the tree is dropped.
TabStops
public IReadOnlyList<string> TabStops();The ids of the controls Tab visits, in tree order - what a host walks when it hands Tab on to its own tab order after the last one. Readable parts are no tab stops.
Tick
public void Tick(double dtMs);Moves every control that animates (Control.WantsTick) on by dtMs. A control at rest is not ticked: the frames a host draws for another reason - a fade elsewhere, the repaint flash - must not repaint it.
Events
FocusChanged
public event Action? FocusChanged;
PartsChanged
public event Action? PartsChanged;The parts changed their shape without the root changing: controls joined or left (the cards of a feed near the view). A host's automation peers ask for the children again.
