Table of Contents

Class Selector

Namespace
Icy.UI.Controls
Assembly
IcyUI.dll

Adds single selection and a popup-hosted item list on top of SelectingItemsControl - the shared base for Dropdown and ComboBox.

public abstract class Selector : SelectingItemsControl, INotifyPropertyChanging, IBindingTarget, INotifyPropertyChanged, INotifyFocusChanged, IContainerLayout, IVirtualizingScrollInfo
Inheritance
Selector
Implements
Derived
Inherited Members
Extension Methods

Remarks

Realizes SelectorItems (see CreateContainer(DataTemplate, object)) instead of bare ItemContainers. Chrome shows only the always-visible closed-state row (PART_ToggleButton, plus PART_TextBox for ComboBox); realized items live in a separate popup host added to the owning Canvas's Overlays only while IsOpen - see RealizationBounds/AttachContainer(ItemContainer, int)/ DetachContainer(ItemContainer), which redirect ItemsControl's existing realize/de-realize/virtualization algorithm there without changing it.

Keeps a single Canvas-level focus point for the whole interaction (itself for Dropdown, a text box for ComboBox - see HookFocusGate(UIElement)) and never moves focus onto individual SelectorItems.

Constructors

Selector()

Initializes a new instance of the Selector class.

protected Selector()

Properties

DisplayMemberPath

Gets or sets the path (resolved dynamically, tolerant of heterogeneous/unknown item types) used to derive each item's display text - GetDisplayText(object) resolves it, falling back to ToString() when unset or when resolution fails for a given item.

public string? DisplayMemberPath { get; set; }

Property Value

string

HighlightedIndex

Gets or sets the popup's current keyboard/gamepad-highlighted index, or -1 for none. Not a public property - purely the "currently highlighted, not yet committed" cursor a subclass's navigation handling drives.

protected int HighlightedIndex { get; set; }

Property Value

int

IsOpen

Gets or sets a value indicating whether this Selector's popup is currently shown.

public bool IsOpen { get; set; }

Property Value

bool

MaxDropDownHeight

Gets or sets the maximum height, in pixels, the open popup grows to before an internal ScrollViewer takes over.

[Range(0, Infinity, MinimumIsExclusive = true)]
public float MaxDropDownHeight { get; set; }

Property Value

float

Remarks

Must be greater than 0.

PopupBackground

Gets or sets the background brush of the open popup.

public IBrush PopupBackground { get; set; }

Property Value

IBrush

Remarks

The popup is hosted in the owning Canvas's Overlays, outside this control's own Template/Chrome, so it can't be decorated by a template - this property (and PopupBorderBrush/PopupBorderThickness) is how a Style reaches it.

PopupBorderBrush

Gets or sets the border brush of the open popup.

public IBrush? PopupBorderBrush { get; set; }

Property Value

IBrush

Remarks

See PopupBackground for why the popup carries its own decoration properties.

PopupBorderThickness

Gets or sets the border thickness of the open popup.

public Thickness PopupBorderThickness { get; set; }

Property Value

Thickness

Remarks

See PopupBackground for why the popup carries its own decoration properties.

RealizationBounds

Gets the rectangle realized containers are positioned within - ContentBounds by default. A subclass that displays realized containers somewhere other than its own content area (e.g. a popup) overrides this to redirect positioning, without changing the realize/de-realize/virtualization algorithm itself.

protected override Rectangle RealizationBounds { get; }

Property Value

Rectangle

RealizesWithoutScrollHost

Gets a value indicating whether this control realizes items within its own content area when no scroll host reports a viewport. true by default.

protected override bool RealizesWithoutScrollHost { get; }

Property Value

bool

Remarks

A control that realizes its items somewhere else (a Selector realizes them into its popup) overrides this to return false.

SelectedValue

Gets the value SelectedValuePath resolves from SelectedItem - null when SelectedItem/SelectedValuePath is null, or when the path fails to resolve for the current item.

[Browsable(false)]
public object? SelectedValue { get; }

Property Value

object

SelectedValuePath

Gets or sets the path (resolved dynamically) used to derive SelectedValue from SelectedItem.

public string? SelectedValuePath { get; set; }

Property Value

string

Toggle

Gets the popup's toggle button - always live, whether the built-in default or a Template's own PART_ToggleButton.

protected ToggleButton Toggle { get; }

Property Value

ToggleButton

Methods

AttachContainer(ItemContainer, int)

Stamps a freshly realized container's IsSelected from SelectedIndex, on top of the base ItemsControl wiring - the case the SelectedIndex setter's own stamp (see its own realizedContainers lookup) can't reach: index selected before it's ever realized, or re-realized later after a pool-and-reuse cycle. Selector overrides this again itself, to route into its popup host instead of this base's default parenting.

protected override void AttachContainer(ItemContainer container, int index)

Parameters

container ItemContainer

The freshly realized container.

index int

The item index container was realized for.

Remarks

Also wires up click-to-select: subscribes Tapped (handled by Container_Tapped(object, EventArgs)) and records index in ContainerIndices so the handler can look it back up. This is inert for Selector and its subclasses (Dropdown, ComboBox), since their own AttachContainer override doesn't call this base implementation.

DetachContainer(ItemContainer)

Exact inverse of AttachContainer(ItemContainer, int)'s click-to-select wiring - unsubscribes Tapped and forgets container's Icy.UI.Controls.SelectingItemsControl.containerIndices entry before deferring to the base detach.

protected override void DetachContainer(ItemContainer container)

Parameters

container ItemContainer

The container being de-realized.

GetDisplayText(object)

Resolves DisplayMemberPath against item, falling back to ToString() when unset or unresolvable.

protected string GetDisplayText(object item)

Parameters

item object

The item to derive display text for.

Returns

string

The resolved display text.

GetVisualChildren()

Enumerates this element's immediate visual children, in paint order (back-to-front), for hit-testing and tree traversal.

protected override IEnumerable<UIElement> GetVisualChildren()

Returns

IEnumerable<UIElement>

The element's immediate children. The base implementation yields none (a leaf element).

Remarks

Container elements (e.g. Panel, Border) override this to expose their children, in the exact order they're drawn in — HitTest(Vector2) depends on this matching actual paint order, or clicks will target the wrong, visually-obscured element.

HookFocusGate(UIElement)

Re-points which element's FocusChanged gates the Escape/gamepad-B subscription (see SubscribeNavigation()) - this by default (wired once, at construction). ComboBox calls this again with its own text box, since that's the element that actually holds Canvas focus for it.

protected void HookFocusGate(UIElement gate)

Parameters

gate UIElement

The element whose focus state should drive the subscription from now on.

Remarks

Arrows and Enter don't depend on the gate: they arrive through OnNavigate(Vector2) and OnActivate(), routed by Canvas from the focused element up through its ancestors.

MeasureContent()

Handles custom measure logic when overridden in any UIElement instance.

protected override Size MeasureContent()

Returns

Size

Size recommended to display the UIElement.

Remarks

This method is called during the measure pass to determine the desired size of the element. The returned size should represent the natural size of the element's content, without considering any constraints, margins, or padding.

When implementing this method:

  • Return the natural size needed to display the content;
  • Do not include padding, margins, or borders in the returned size;
  • Consider the content's actual dimensions (e.g., text length, image size);
  • Return Empty if the element has no content.

OnActivate()

Invoked by Canvas for an activation press (Enter, gamepad A) while this element or one of its descendants has focus.

protected override bool OnActivate()

Returns

bool

true to claim the press; Canvas then stops calling further ancestors. The base implementation returns false.

Remarks

Closed: opens the popup. Open with a valid HighlightedIndex: commits it to SelectedIndex and closes. Always claims the press.

OnApplyTemplate()

Called once, every time Template changes (in either direction - applied, cleared, or switched to a different template), right after Chrome and every decoration/content property have been fully re-wired for the new state.

protected override void OnApplyTemplate()

Remarks

Does nothing by default. A subclass that reaches into its own visual tree by reference - e.g. Slider wiring its drag logic directly to its thumb element - overrides this to call GetTemplateChild<T>(string) and repoint that reference to whatever the new Template (or, when reverting, the fresh default Border) actually provides, instead of hardcoding it once at construction. This is what actually makes a control "template-safe": until a control overrides this appropriately, applying a custom Template to it silently orphans whatever elements its own code still holds direct references to.

OnArrangeUpdated()

Raises the ArrangeUpdated event.

protected override void OnArrangeUpdated()

Remarks

Re-positions an open popup, so it stays attached when this control moves - e.g. after a window resize or a EffectiveScale change re-lays out the surface. The reposition is deferred until every root element is arranged (this control's ancestors are still mid-arrange here) and runs before the canvas arranges its overlays, so the popup lands in the right place within the same frame.

OnAttached()

Raises the Attached event.

protected override void OnAttached()

Remarks

Propagates Canvas to every element in GetVisualChildren() - this is what makes attaching a whole pre-built subtree (the common pattern: build children, then attach the root once via Add(UIElement)) reach every descendant, not just the immediate root. Each child's own Canvas setter recurses into its children the same way, so this walks the full subtree regardless of depth. Container-specific child-add paths (e.g. OnChildAdded(object?, GenericEventArgs<UIElement>)) still assign Canvas directly for children added after the parent is already attached - this cascade is what covers everything built beforehand.

OnDetached()

Raises the Detached event.

protected override void OnDetached()

Remarks

Propagates null to every element in GetVisualChildren(), mirroring OnAttached() - see its remarks.

OnNavigate(Vector2)

Invoked by Canvas for a directional navigation press (arrow keys, D-pad, left stick) while this element or one of its descendants has focus.

protected override bool OnNavigate(Vector2 direction)

Parameters

direction Vector2

The pressed direction: one of the four unit vectors, in UI space (+Y down).

Returns

bool

true to claim the press. Canvas calls the focused element first, then each ancestor, and stops at the first claim. When nothing claims it, focus moves to the nearest control in direction (see MoveFocus(Vector2)). The base implementation returns false.

Remarks

Claim only what you use, and let presses go at your edges (a list at its last row, a caret at the end of its text), so focus can move on to the next control.

OnNavigationCloseModal(object?, EventArgs)

Handles Escape/gamepad-B while the focus gate is focused - closes the popup without changing SelectedIndex.

protected virtual void OnNavigationCloseModal(object? sender, EventArgs e)

Parameters

sender object

The source of the event.

e EventArgs

The EventArgs instance containing the event data.

OnRender(IRenderContext)

Handles custom render logic when overridden in any UIElement instance.

protected override void OnRender(IRenderContext context)

Parameters

context IRenderContext

The context to render with.

OnSelectionChanged()

Called after SelectedIndex/SelectedItem have been updated, before the realized-container stamp and the public SelectionChanged event - empty by default. A subclass with derived state of its own that depends on the new selection (e.g. Selector's SelectedValue) overrides this to refresh it, rather than reacting to its own SelectionChanged subscription (which would depend on subscriber-ordering relative to any external subscriber this control's own consumer adds).

protected override void OnSelectionChanged()

RefreshPopupPosition()

Re-runs the popup's position/size calculation if it's currently open - a no-op otherwise. A subclass calls this after something that changes how much space the popup needs (e.g. ComboBox after filtering changes the item count).

protected void RefreshPopupPosition()

SubscribeNavigation()

Subscribes to CloseModal (Escape/gamepad B) - idempotent, and a no-op while unattached. Arrows and Enter arrive through OnNavigate(Vector2) and OnActivate() instead.

protected void SubscribeNavigation()

UnsubscribeNavigation()

Unsubscribes from CloseModal (Escape/gamepad B) - idempotent. Arrows and Enter arrive through OnNavigate(Vector2) and OnActivate() instead.

protected void UnsubscribeNavigation()