Table of Contents

Class Control

Namespace
Icy.UI.Controls
Assembly
IcyUI.dll

Base class for controls that need background/border decoration without reimplementing it - composes an internal element (Chrome, a Border by default) that fills this control's entire ActualBounds and forwards Background/BorderBrush/ BorderThickness/Padding to it.

public class Control : UIElement, INotifyPropertyChanging, IBindingTarget, INotifyPropertyChanged, INotifyFocusChanged, IContainerLayout
Inheritance
Control
Implements
Derived
Inherited Members
Extension Methods

Remarks

UIElement itself carries no decoration (per the Border extraction) - this class exists so every decorated control doesn't have to compose its own Border by hand. Subclasses that need child content should derive from ContentControl instead, which exposes Content as Chrome's content.

Setting Template replaces Chrome with markup content of the template author's choosing instead of the built-in Border - see Template's own remarks. Any future property added to this class that currently forwards into Chrome the way Background/BorderBrush/BorderThickness/Padding do must gain the same dual-path treatment those four have (an untemplated branch forwarding into (Border)Chrome, a templated branch storing locally) - there is no generic mechanism that does this automatically, so it's easy to forget.

The untemplated branch of that same dual-path treatment also participates in the value-precedence system (see PropertyValuePrecedence): a direct C# assignment to Background/BorderBrush/BorderThickness/Padding while untemplated now raises this control's own PropertyChanged (guarded by an equality check, so re-assigning the current value is a no-op), so it registers as a local value that outranks a later-applied Style/VisualState/animation setter for the same property - exactly like every other [RegisterReference] property on this class. A local value blocks every precedence tier below it outright, not just Style - a control that locally sets one of these four properties in its own constructor (the common pattern for a custom-themed sub-widget, e.g. a Slider's gradient-filled Background) permanently opts that property out of its type's own theme, including any VisualState that targets it.

Constructors

Control()

Initializes a new instance of the Control class.

public Control()

Properties

Background

Gets or sets the background brush drawn behind this control's content.

public IBrush Background { get; set; }

Property Value

IBrush

BorderBrush

Gets or sets the brush used to paint this control's border.

public IBrush? BorderBrush { get; set; }

Property Value

IBrush

BorderThickness

Gets or sets the thickness of this control's border, and the amount by which its content is inset from ContentBounds.

public Thickness BorderThickness { get; set; }

Property Value

Thickness

Chrome

Gets the internal element this control composes for its background/border decoration - a Border by default, or Template's instantiated content when one is set.

protected UIElement Chrome { get; }

Property Value

UIElement

ContentBounds

Gets the area available to this control's decorated content, i.e. this control's own ActualBounds inset by BorderThickness and Padding.

public Rectangle ContentBounds { get; }

Property Value

Rectangle

Remarks

Computed from this control's own ActualBounds, not Chrome's - normally the two agree (Chrome fills ActualBounds exactly), but a subclass like ScrollViewer deliberately arranges Chrome larger than this control's own bounds (so Content can overflow for scrolling) - callers of ContentBounds mean this control's own visible content area, not however big Chrome currently happens to be.

Padding

Gets or sets the padding of this control, i.e. the amount by which its content is inset from Chrome's own bounds (inside BorderThickness).

public Thickness Padding { get; set; }

Property Value

Thickness

Remarks

Hides (rather than overrides - Padding isn't virtual) Padding to forward it to Chrome when untemplated, the same way Background/BorderBrush/BorderThickness already do - so Background/BorderBrush (drawn across Chrome's full bounds, unaffected by padding - the standard box model) cover this control's whole area including the padding band, and only the actual content ends up inset by it. Measure()'s own generic padding step still applies exactly once when untemplated - to Chrome, when MeasureContent() calls Chrome's Measure() - since this control's own (hidden, always-zero) base Padding field is never written to.

Template

Gets or sets the template that replaces this control's default decorated visual tree (Chrome).

public ControlTemplate? Template { get; set; }

Property Value

ControlTemplate

Remarks

null (the default) keeps this control's built-in Border-based decoration, with Background/BorderBrush/BorderThickness/ Padding forwarding into it exactly as always. Setting a template swaps Chrome for markup content of the template author's choosing instead - those same four properties then store locally, reflected into the template's content only where it explicitly asks for them via {TemplateBinding} (see TemplateBindingExtension). Their values are preserved across the switch either way.

Simple decorated controls like Button/ContentControl/CheckBox/ ToggleButton are always template-safe. Structural controls whose own code reaches into their visual tree by name - such as Slider (PART_Thumb) or ProgressBar (PART_Fill) - are safe too, via named template parts: see OnApplyTemplate()/GetTemplateChild<T>(string). A control that hasn't been retrofitted with an OnApplyTemplate() override yet - currently ScrollViewer, TextBox, ColorPicker, and ColorPickerButton - still accepts a Template without throwing, but its own behavior stays wired to its built-in default parts regardless of what the template actually provides.

Exceptions

ArgumentException

value's own target type isn't assignable to this control's runtime type.

Methods

ArrangeContent()

Handles custom arrange logic when overridden in any UIElement instance.

protected override void ArrangeContent()

Remarks

This method is called during the arrange pass to position and size the element's content within its final bounds. The element's position and size are already determined by the layout system and available in the ActualBounds property.

When implementing this method:

  • Position child elements within the available space;
  • Respect the element's padding when positioning content;
  • Handle any content-specific layout requirements;
  • Call Arrange() on child elements if needed.

CaptureTemplateState()

Captures whatever subclass-specific state needs to survive a Template swap, beyond the four decoration properties this class already preserves on its own.

protected virtual object? CaptureTemplateState()

Returns

object

Opaque state later passed back to RestoreTemplateState(object?).

Remarks

Returns null by default. ContentControl overrides this pair to also preserve Content across a swap. This is also the seed of what a future named-template-parts addition is expected to build on: it runs once per swap, at whichever of the two points RestoreTemplateState(object?)'s own remarks describe - exactly where discovering a new template's named parts would need to happen too.

GetHostedElement(object?)

Gets the element, if any, that the old Chrome displays but doesn't own, so a Template swap leaves its bindings alone.

protected virtual UIElement? GetHostedElement(object? state)

Parameters

state object

The state CaptureTemplateState() returned before the swap.

Returns

UIElement

The hosted element, or null for none. Returns null by default.

Remarks

A swap unbinds every element of the old Chrome, so its {TemplateBinding}s stop following this control. Elements the control only hosts there belong to the application, and so do their {Binding}s. They move on to the new Chrome and must keep working.

ContentControl returns its Content.

GetTemplateChild<T>(string)

Finds a named element inside the current Template's content - a "template part".

protected T? GetTemplateChild<T>(string name) where T : UIElement

Parameters

name string

The name to look up, as declared by x:Name on an element inside the template's markup. No naming convention is enforced by this method itself, but the established one (mirroring WPF) is a PART_ prefix, e.g. PART_Thumb - document it on whichever property/field a subclass exposes the found element through.

Returns

T

The named element, or null when Template is null, the template has no element by that name, or it isn't a T.

Type Parameters

T

The type the named element is expected to have.

Remarks

Deliberately does not reuse FindControl<T>(UIElement, string)

  • that extension searches from element.GetRoot(), which would walk straight past Chrome up to whatever document this control itself is attached inside, missing the template's own isolated MarkupNameScope entirely (or, worse, silently matching an unrelated same-named element elsewhere in that outer document). This looks up GetScope(UIElement) directly on Chrome instead, which is exactly the root LoadTemplateContent(XElement, UIElement, string?) attaches the template's own scope to.

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.

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.

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 virtual 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.

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.

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.

RestoreTemplateState(object?)

Restores state captured by CaptureTemplateState() after a Template swap.

protected virtual void RestoreTemplateState(object? state)

Parameters

state object

The state CaptureTemplateState() returned before the swap.

Remarks

Called exactly once per swap, at one of two different points depending on direction - check Template (already the new value by the time this runs) to tell which:

  • Switching to a template (Template is non-null): called before the new template's content is built, so a {TemplateBinding} inside it captures the restored value on its very first read. Restore state directly into a backing field here, never through a public property setter that participates in the value-precedence system (mirrors how this class's own Background/BorderBrush/BorderThickness/ Padding are restored - see Template's setter for why).
  • Switching away from a template (Template is null): called after Chrome has already been replaced by the fresh default Border - restore state into that Border's own properties directly; it's brand new, so there's no precedence-poisoning risk there.