xaml-csharp-development-skill-for-avalonia

Property System, Attached Properties, Behaviors, and Style Properties

Table of Contents

  1. Scope and APIs
  2. Avalonia Property System
  3. Property Identity, Flags, and Metadata Lookup
  4. Metadata and Binding Defaults
  5. Effective Values, Base Values, and Priority-Safe Updates
  6. Change Events and Registry Diagnostics
  7. Attached Property Authoring
  8. Behavior Patterns
  9. Style Property Authoring
  10. Container Queries (Media Query Equivalent)
  11. Best Practices
  12. Troubleshooting
  13. XAML-First and Code-Only Usage

Scope and APIs

Primary APIs:

Reference source files:

Avalonia Property System

Property flavors:

Read/write APIs:

Choose registration based on need:

Property Identity, Flags, and Metadata Lookup

Every AvaloniaProperty exposes identity and capability flags:

Metadata and validation query APIs:

Indexer helper operators:

Unset marker semantics:

Administrative cleanup API:

Example:

AvaloniaProperty property = TextBox.TextProperty;
string name = property.Name;
Type propertyType = property.PropertyType;
Type ownerType = property.OwnerType;

AvaloniaPropertyMetadata metadataByType = property.GetMetadata(typeof(TextBox));
AvaloniaPropertyMetadata metadataByInstance = property.GetMetadata(textBox);

object? candidate = "hello";
if (!property.IsValidValue(candidate))
    candidate = AvaloniaProperty.UnsetValue; // UnsetValueType marker

var descriptor = ~property;

Metadata and Binding Defaults

AvaloniaPropertyMetadata controls binding and validation behavior at registration/owner level.

Key points:

StyledProperty<T> advanced APIs:

DirectProperty<TOwner, TValue> advanced owner extension:

Metadata lifecycle APIs (mainly for control/framework authors):

Use these APIs to tune property behavior per control family, not per instance.

Notes:

Effective Values, Base Values, and Priority-Safe Updates

Important distinction:

Useful APIs:

UnsetValue versus null:

Example:

if (control.IsSet(MyControl.ValueProperty))
{
    var baseValue = control.GetBaseValue(MyControl.ValueProperty).GetValueOrDefault();
    // Use baseValue for diagnostics or non-animated logic paths.
}

control.SetCurrentValue(MyControl.ValueProperty, 42);
control.CoerceValue(MyControl.ValueProperty);

When updating control-owned properties at runtime, prefer SetCurrentValue over SetValue to avoid accidentally breaking app-declared bindings and style precedence.

Change Events and Registry Diagnostics

Change event APIs:

Typed constructor path for manual advanced scenarios:

Registry APIs (diagnostics/tooling oriented):

Practical uses:

Example:

control.PropertyChanged += (_, e) =>
{
    if (e.Property == TextBox.TextProperty)
    {
        string? oldText = e.GetOldValue<string?>();
        string? newText = e.GetNewValue<string?>();
    }
};

var registry = AvaloniaPropertyRegistry.Instance;
bool isRegistered = registry.IsRegistered(typeof(TextBox), TextBox.TextProperty);

Attached Property Authoring

Canonical pattern:

public static class FocusAssist
{
    public static readonly AttachedProperty<bool> HighlightWhenFocusedProperty =
        AvaloniaProperty.RegisterAttached<FocusAssist, Control, bool>("HighlightWhenFocused");

    static FocusAssist()
    {
        HighlightWhenFocusedProperty.Changed.AddClassHandler<Control>((control, e) =>
        {
            if (e.GetNewValue<bool>())
                control.Classes.Add("focus-highlight");
            else
                control.Classes.Remove("focus-highlight");
        });
    }

    public static bool GetHighlightWhenFocused(Control control) =>
        control.GetValue(HighlightWhenFocusedProperty);

    public static void SetHighlightWhenFocused(Control control, bool value) =>
        control.SetValue(HighlightWhenFocusedProperty, value);
}

XAML usage:

<TextBox local:FocusAssist.HighlightWhenFocused="True" />

Behavior Patterns

Avalonia core does not provide a built-in Behavior<T> base in this repository.

Preferred behavior patterns in core apps:

Use cases:

Style Property Authoring

In styles/themes:

Typed C# selector example:

var style = new Style(x => x.OfType<Button>().Class("primary"));
style.Setters.Add(new Setter(Button.FontWeightProperty, FontWeight.SemiBold));

Container Queries (Media Query Equivalent)

Avalonia equivalent of media-query-like behavior uses ContainerQuery.

Key APIs:

Example from sample style pattern:

<ContainerQuery Name="Host" Query="min-width:800">
  <Style Selector="UniformGrid#ContentGrid">
    <Setter Property="Columns" Value="3" />
  </Style>
</ContainerQuery>

<Border Container.Name="Host" Container.Sizing="Width" />

Best Practices

Troubleshooting

  1. Setter value invalid for property:
    • Type mismatch or unsupported value conversion.
  2. Direct property cannot be set from style:
    • Direct properties with style activators can be restricted; use styled property when style-driven changes are required.
  3. Attached property appears ignored:
    • Getter/setter type mismatch or wrong host type in registration.
  4. Container query not triggering:
    • Missing Container.Name or Container.Sizing on expected container.
    • Query condition does not match current container dimensions.

XAML-First and Code-Only Usage

Default mode:

XAML-first complete example:

<StackPanel xmlns="https://github.com/avaloniaui"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="using:MyApp.Behaviors"
            Spacing="8">
  <TextBox local:FocusAssist.HighlightWhenFocused="True" />
  <Button Classes="primary" Content="Submit" />
</StackPanel>

Code-only alternative (on request):

using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Styling;

var text = new TextBox();
FocusAssist.SetHighlightWhenFocused(text, true);

var style = new Style(x => x.OfType<Button>().Class("primary"));
style.Setters.Add(new Setter(Button.FontWeightProperty, FontWeight.SemiBold));
Application.Current!.Styles.Add(style);