Class Selector
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
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
IsOpen
Gets or sets a value indicating whether this Selector's popup is currently shown.
public bool IsOpen { get; set; }
Property Value
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
Remarks
Must be greater than 0.
PopupBackground
Gets or sets the background brush of the open popup.
public IBrush PopupBackground { get; set; }
Property Value
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
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
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
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
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
SelectedValuePath
Gets or sets the path (resolved dynamically) used to derive SelectedValue from SelectedItem.
public string? SelectedValuePath { get; set; }
Property Value
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
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
containerItemContainerThe freshly realized container.
indexintThe item index
containerwas 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
containerItemContainerThe container being de-realized.
GetDisplayText(object)
Resolves DisplayMemberPath against item, falling back to
ToString() when unset or unresolvable.
protected string GetDisplayText(object item)
Parameters
itemobjectThe 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
gateUIElementThe 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
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
directionVector2The 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
senderobjectThe source of the event.
eEventArgsThe 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
contextIRenderContextThe 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()