Skip to content

UI Controls

Mike Strobel edited this page Aug 4, 2026 · 3 revisions

Controls

Cursorial ships a full WPF/Avalonia-style control catalog in the Cursorial.UI.Controls namespace — buttons, text input, lists, menus, tabs, date pickers, breadcrumb trails, column list views, completion overlays, and the layout/presentation primitives they're built from. This page is the catalog: what each control is, the one or two headline properties you'll set most, and a tiny example for the common ones. Reach here when you know you want a control and need its API; reach for UI Overview when you want the bigger picture.

If you've used WPF or Avalonia, the shapes will feel familiar: every control derives from Control, carries a ControlTemplate, and is restyled with the same selector/setter machinery described in Styling and themes. Looks come from the built-in theme — you author behavior in C# or XAML and the theme paints it in a box-drawing idiom that adapts to the terminal's color depth.

Namespace note. Controls, Panel/panels, and presenters live in Cursorial.UI.Controls. The base UIElement, the property system, and the alignment enums live in Cursorial.UI. Brushes and pens are in Cursorial.Drawing.Media.


Base classes and presentation

These aren't controls you'd place directly so much as the building blocks every other control extends.

  • Control — the base of every templated control. Headline properties: Template (a ControlTemplate), Background/Foreground (IBrush?), BorderPen (Pen?), Padding (Margins), ContextMenu. The visual tree is built from Template at measure time.
  • ContentControl — a control with a single Content (object?) rendered through a ContentTemplate (DataTemplate?). Set Content to a string, a UIElement, or any object plus a matching template. Also carries ContentStringFormat and Horizontal/VerticalContentAlignment. (HeaderedContentControl adds Header/ HeaderTemplate.)
  • ContentPresenter — the part a ContentControl's template uses to actually place its Content. Picks a DataTemplate by content type when no explicit ContentTemplate is set; RecognizesAccessKey folds _ mnemonics in string content.
  • Decorator — a single-Child (UIElement?) wrapper, the base for Border.
  • DrawnContentPresenter — the abstract base for presenters that paint their content straight through the drawing layer instead of composing child elements. Concrete presenters you can use directly inside a template: GlyphPresenter (a single themed glyph), ImagePresenter (an inline Image's pixels), FigletPresenter (large banner text in a FigletFont — Text, Font, TextAlignment, TextTrimming, FillEntireBounds), and RichTextPresenter (a RichText/markup value via Source, with TextTrimming/TextAlignment). These are building blocks for custom looks; the everyday TextBlock and Icon are the controls you reach for first.
var card = new ContentControl
{
    Content = "Hello, terminal",
    Padding = new Margins(1),
};

Text and display

  • TextBlock — read-only text. Set Text (plain), or Markup for inline-styled spans. Tune with TextWrapping (WrapMode), TextAlignment, TextTrimming, and the inherited Foreground.
  • Icon — a capability-tiered glyph. Provide any of Glyph (a Nerd Font codepoint), ImageUri/Image (an inline image), Emoji, and Text (a Unicode floor); at render time it resolves the highest-preference tier the terminal actually supports — Nerd Font → graphics protocol → color emoji → text. GlyphWidth sizes a wide glyph. Usable standalone (<Icon Glyph="…" Emoji="📁" Text="F"/>), as a MenuItem.Icon, or via the terse {Icon …} markup form.
  • Border — a Decorator that draws a box around its Child. Headline properties: BorderPen (Pen?), Background (IBrush?), Padding (Margins), and an optional Title/TitlePosition for a titled panel.
  • Label — a ContentControl whose access key (_) moves focus to its Target (UIElement?).
  • Separator — a thin divider line; set Orientation.
var heading = new TextBlock("Settings") { TextWrapping = WrapMode.Wrap };

var framed = new Border
{
    Title = "Details",
    Padding = new Margins(1),
    Child = heading,
};

Buttons and toggles

All of these derive from ButtonBase, which carries Command (ICommand?), CommandParameter, ClickMode, the read-only IsPressed, and the bubbling Click event (EventHandler<ClickEventArgs>). They activate on the spacebar and Enter as well as the mouse. Every clickable control implements IClickableControl (the Click event, RaiseClick(InvokeMethod), and IsEffectivelyEnabled), so a host can invoke any of them uniformly.

  • Button — the standard push button. Adds IsDefault/IsCancel (Enter/Escape activation hints for dialogs).
  • RepeatButton — fires Click repeatedly while held; tune Delay and Interval (milliseconds).
  • ToggleButton — a two- or three-state toggle. IsChecked is bool? (the third, null, state is enabled via IsThreeState); raises Checked/Unchecked/Indeterminate.
  • CheckBox — a ToggleButton rendered as a checkbox.
  • RadioButton — a ToggleButton with mutual exclusion across a shared GroupName.
var save = new Button { Content = "_Save", Command = saveCommand };
save.Click += (_, _) => Console.Beep();

var wrap = new CheckBox { Content = "Word wrap", IsChecked = true };

var small  = new RadioButton { Content = "Small",  GroupName = "size" };
var medium = new RadioButton { Content = "Medium", GroupName = "size", IsChecked = true };

Text input

  • TextBox — single-line editable text. Headline: Text (string), plus IsReadOnly, MaxLength, Placeholder, and SelectionBrush. Exposes SelectedText, SelectAll(), Clear(), and a TextChanged event.
  • PasswordBox — a TextBox that masks input. Password mirrors Text; PasswordChar is the mask glyph and RevealPassword un-masks.
var name = new TextBox { Placeholder = "Your name", MaxLength = 40 };
name.TextChanged += (_, _) => Validate(name.Text);

var pin = new PasswordBox { PasswordChar = '•' };

Range controls

RangeBase is the shared base for value-over-a-range controls: Minimum, Maximum, Value (all double), plus SmallChange/LargeChange.

  • Slider — a draggable thumb over a track. Adds Orientation and FilledPen (the styling of the filled portion); Value/Minimum/Maximum come from RangeBase.
  • ProgressBar — a determinate bar driven by Value, or set IsIndeterminate for an animated marquee. Fill styles the bar brush.
  • ScrollBar — usually composed by ScrollViewer, but available standalone: Orientation, ViewportSize, ThumbBrush, and a Scroll event.
var volume   = new Slider { Minimum = 0, Maximum = 100, Value = 70 };
var progress = new ProgressBar { Minimum = 0, Maximum = 1, Value = 0.4 };
var spinner  = new ProgressBar { IsIndeterminate = true };

Scrolling

  • ScrollViewer — a ContentControl that scrolls oversized Content. Set VerticalScrollBarVisibility / HorizontalScrollBarVisibility (ScrollBarVisibility); read HorizontalOffset/VerticalOffset (cells) and the Extent/Viewport sizes. Scrolling within the cached band is a pure composite slide — fast even for tall content.
var viewer = new ScrollViewer
{
    VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
    Content = bigTextBlock,
};

Items controls and selection

ItemsControl is the base for everything that displays a collection. Bind a collection to ItemsSource (IEnumerable?) or add to the inline Items collection — not both. ItemTemplate (DataTemplate?) renders each item, ItemsPanel (ItemsPanelTemplate?) chooses the layout panel, and ItemContainerStyle styles the generated containers. IsTextSearchEnabled adds type-to-find.

Selecting variants derive from SelectingItemsControl, which adds SelectedItem (object?), SelectedIndex (int), SelectedItems, SelectionMode, and the SelectionChanged/ItemActivated events.

  • ListBox — a selectable, scrollable list of ListBoxItems.
  • ListView — a selectable list with switchable view modes (the file-manager control; see the subsection below). Distinct from the DataViews DataGrid — it's a lightweight column/tile list, not a data-shaping grid.
  • ComboBox — a single-select drop-down. Headline: IsDropDownOpen, IsEditable (with Text/PlaceholderText for the editable case), MaxDropDownHeight; selection via the inherited SelectedItem/SelectedIndex.
  • TreeView — a hierarchical tree of TreeViewItems. Tree-wide single selection lives on TreeView.SelectedItem (read-only); each TreeViewItem exposes IsExpanded and IsSelected. Use a HierarchicalDataTemplate to bind nested data.
  • TabControl — a tabbed view of TabItems. The selected tab is the inherited SelectedItem; SelectedContent (read-only) is its content. Each TabItem is a HeaderedContentControl with Header + Content and an IsSelected.
  • StatusBar — a horizontal strip of StatusBarItems (an ItemsControl), typically docked at the bottom.
var list = new ListBox { ItemsSource = files };
list.SelectionChanged += (_, e) => Open(list.SelectedItem);

var combo = new ComboBox { ItemsSource = themes, SelectedIndex = 0 };

var tabs = new TabControl();
tabs.Items.Add(new TabItem { Header = "_General",  Content = generalPage });
tabs.Items.Add(new TabItem { Header = "_Advanced", Content = advancedPage });

Column list views (ListView)

ListView is the file-manager list: one control with four presentations, switched at runtime through View (ListViewViewMode) — Details (a column grid under a clickable header row), List and SmallIcons (single-line names wrapping column-major with horizontal scroll), and Tiles (a two-line tile wrapping row-major). It derives from SelectingItemsControl, so selection and ItemsSource work exactly as on ListBox; containers are ListViewItems and it adds an ItemInvoked event (Enter or double-click — "open this", distinct from selection).

In Details mode you populate the Columns collection (ListViewColumnCollection) with ListViewColumns. A column is a UIObject description (not a UIElement): headline properties are Header, Width (a GridLength — Auto/fixed/Star, read just like a Grid track), MinWidth, DisplayMemberPath (or a CellTemplate), and HorizontalAlignment. Header clicks cycle the sort through the clickable ListViewColumnHeader: the control raises the vetoable Sorting event (a host sorts its own collection), and opting into IsBuiltInSortEnabled runs a built-in comparer keyed on the column's SortMemberPath/DisplayMemberPath. SortColumn/SortDirection (ListViewSortDirection) are settable so a restored layout can light the right indicator; CycleSort(column) and ApplySort(column, direction) drive the same funnel from a menu item. The non-Details views read DisplayMemberPath, IconMemberPath, and SecondaryMemberPath, and size tiles via ItemWidth/ItemHeight (forwarded to the UniformWrapPanel that hosts them). v1 is non-virtualizing.

var files = new ListView { ItemsSource = entries, View = ListViewViewMode.Details };
files.Columns.Add(new ListViewColumn { Header = "Name", Width = GridLength.Star, DisplayMemberPath = "Name" });
files.Columns.Add(new ListViewColumn
{
    Header = "Size",
    Width = GridLength.Auto,
    DisplayMemberPath = "Size",
    HorizontalAlignment = HorizontalAlignment.Right,
});
files.ItemInvoked += (_, e) => Open(e.Item);
files.Sorting     += (_, e) => SortEntriesBy(e.Column.EffectiveSortMemberPath, e.Direction);

Item containers and virtualization

ItemsControl.ItemContainerGenerator maps between data items and their generated containers — ContainerFromIndex, IndexFromContainer, and ContainerCount. For long lists, opt a ListBox (or any items control) into a VirtualizingStackPanel and turn on virtualization with the attached properties on VirtualizingPanel: VirtualizingPanel.SetIsVirtualizing(control, true) and VirtualizingPanel.SetVirtualizationMode(control, mode) (VirtualizationMode.Recycling is the default). Only the visible window of containers is realized; selection and keyboard navigation stay item-indexed even when most containers don't exist.

VirtualizingPanel.SetIsVirtualizing(list, true);
VirtualizingPanel.SetVirtualizationMode(list, VirtualizationMode.Recycling);

Breadcrumbs

  • BreadcrumbBar — a hierarchical trail of activatable "chips" with a leading-overflow fold and an optional chips ↔ raw-text edit hand-off (the Windows Explorer address bar). It's an ItemsControl over any hierarchy — a directory trail, an object graph, a navigation history — so ItemsSource, ItemTemplate, and per-chip Command bindings all work as they do on every items control; containers are BreadcrumbBarItems. The bar never mutates itself: activating a segment raises the bubbling ItemActivated event and the host answers by replacing the items. It's one tab stop (arrow keys move the active chip, Home/End jump to the ends, Enter/ Space activate) — moving the highlight never navigates. Fill a separator's sibling drop-down on demand by handling DropDownOpening (add plain objects or richer CompletionItems); the pick comes back on ChildActivated. Opt into IsEditable for the F2 raw-text mode — the bar owns only the state machine and raises EditingStarted/ EditCommitted/EditCanceled (with Text and a KeepEditing veto) at the boundaries, leaving path rendering and parsing to the host. Read-only state: IsEditing, ActiveIndex, HasOverflow/OverflowCount.
  • BreadcrumbBarItem — one chip. It's a ButtonBase, so Command/CanExecute/Click come for free; it adds the :current pseudo-class for the deepest (trailing) segment via IsCurrent.
var trail = new BreadcrumbBar { ItemsSource = pathSegments, IsEditable = true };
trail.ItemActivated  += (_, e) => NavigateTo(e.Item);           // host truncates its model at e.Index
trail.DropDownOpening += (_, e) =>
{
    foreach (var sibling in SiblingsOf(e.Item))
        e.Children.Add(sibling);
};
trail.ChildActivated += (_, e) => NavigateTo(e.SelectedChild);

Completion (autocomplete)

  • CompletionPopup — an IntelliSense-style overlay for a TextBox: a non-focusable popup that opens as the user types, ranks candidates with fuzzy matching, bolds the matched characters, and splices the accepted candidate back into the field. It's not an input control — focus never leaves the field. Wire it with three seams: set Target (the TextBox) and a Provider (ICompletionProvider, or DelegateCompletionProvider for a lambda); optionally supply a CommitHandler (Func<CompletionAccept, CompletionCommit>) to control what accepting does. Headline knobs: MaxVisibleItems (rows before the list scrolls — the MaxDropDownHeight role, in cells), ShowHeader/ShowFooter, FooterContent, FuzzyLabel. Read-only IsOpen/MatchCount/Entries (CompletionEntry), and events Opened/Closed/Committed. Call Refresh() to re-query (the hook for an asynchronous candidate source), Accept(...)/AcceptSelected(...) to accept programmatically, and Close().
  • Picker mode — set Anchor (any element) instead of Target and the same control becomes a target-less, scrollable, fuzzy-filtered chooser hanging off a chip or a button. It grows its own Pattern (typing filters the list), Open() opens it on a cleared pattern, and the choice comes back through the Picked event (CompletionPickedEventArgs, with a KeepOpen for drill-down pickers). This is what BreadcrumbBar's drop-downs ride.

The model types are CompletionItem (Display/InsertText/Icon/KindLabel/Description/Data/SortGroup/ ContinuesSession), CompletionQuery (text + caret + CompletionTrigger), CompletionContext (the replaced span, the filter pattern, and the candidates), and CompletionCommit/CompletionAccept for the commit hook. The list itself is a CompletionList (a ListBox of CompletionListItems) — you rarely touch it directly.

var box   = new TextBox { Placeholder = "field or function" };
var popup = new CompletionPopup
{
    Target = box,
    Provider = new DelegateCompletionProvider(q =>
    {
        var start = q.Text.LastIndexOf(' ', Math.Max(0, q.CaretIndex - 1)) + 1;
        return new CompletionContext(start, q.CaretIndex, q.Text[start..q.CaretIndex], Candidates);
    }),
};
// place `popup` anywhere that outlives the field — typically the same Grid cell (it measures 0×0).

Menus and flyouts

  • Menu — a horizontal menu bar of MenuItems; can register as the application main menu.
  • MenuItem — a HeaderedItemsControl: Header is the label, child MenuItems become a submenu Popup. Headline: Command/CommandParameter, Icon, InputGestureText (the right-aligned shortcut hint), IsCheckable/IsChecked, the read-only IsSubmenuOpen/IsHighlighted, and a Click event.
  • ContextMenu — a right-click flyout. Attach one to any element with ContextMenu.SetMenu(element, menu); it opens on right-click or the Menu key. Open(target, position?)/Close() drive it manually; IsOpen reads state.
var file = new MenuItem { Header = "_File" };
file.Items.Add(new MenuItem { Header = "_Open", InputGestureText = "Ctrl+O", Command = openCommand });
file.Items.Add(new Separator());
file.Items.Add(new MenuItem { Header = "E_xit", Command = exitCommand });

var menuBar = new Menu();
menuBar.Items.Add(file);

Tooltips

  • ToolTip — a hit-transparent, never-focused popup shown on hover.
  • ToolTipService — the attached-property entry point. ToolTipService.SetTip(element, content) is the common case; SetInitialDelay and SetShowOnFocus tune timing and focus behavior. The content can be a string or any UIElement.
ToolTipService.SetTip(saveButton, "Save the current document (Ctrl+S)");

Disclosure and splitters

  • Expander — a HeaderedContentControl whose Content collapses behind its Header. Toggle IsExpanded; raises Expanded/Collapsed.
  • GridSplitter — a draggable bar that resizes adjacent Grid tracks. Set ResizeDirection (GridResizeDirection), ResizeBehavior (GridResizeBehavior), and KeyboardIncrement. (Grid layout itself is covered in Layout and panels.)
var details = new Expander { Header = "Advanced options", IsExpanded = false, Content = advancedPanel };

Dates

  • Calendar — an inline month-view picker. Headline: SelectedDate (DateOnly?), DisplayDate (DateOnly), FirstDayOfWeek, DisplayDateStart/DisplayDateEnd bounds, BlackoutDates, IsTodayHighlighted, and a DisplayMode. Raises SelectedDateChanged/DateCommitted.
  • DatePicker — a date field that drops a Calendar popup. SelectedDate (DateOnly?), DisplayDate, Watermark (placeholder), IsDropDownOpen, IsEditable; raises SelectedDateChanged.
var picker = new DatePicker
{
    Watermark = "yyyy-mm-dd",
    SelectedDate = DateOnly.FromDateTime(DateTime.Today),
};
picker.SelectedDateChanged += (_, e) => Schedule(e.NewDate);

Templating and theming

Every control's look is its ControlTemplate — a tree of elements (often a Border wrapping a ContentPresenter) that the control instantiates and binds to with TemplateBinding. The default look comes from the built-in theme, resolved through a control-theme chain so an application Style can override any control wholesale. You rarely write templates by hand for the basics; you reach for them to reskin a control or build a custom one. Selectors, setters, pseudo-classes (:pointerover, :focus, :checked, …), and theme dictionaries are all covered in Styling and themes, and the same trees are expressible in markup — see XAML.

See also

  • UI Overview — the element tree, the application loop, and how a control becomes pixels.
  • Layout and panels — StackPanel, Grid, DockPanel, and how controls are arranged.
  • Styling and themes — selectors, setters, control templates, and theming.
  • Data binding — wiring ItemsSource, SelectedItem, Text, and IsChecked to a view model.

Clone this wiki locally