agentleFS
linuxdevellinuxdevel/Avalonia-skills/skills/avalonia/avalonia-property-system/SKILL.md

avalonia-property-system

Skill50 starsChanged 9 days agoMITView on GitHub

Use when working with Avalonia's property system: StyledProperty, DirectProperty, AttachedProperty, property inheritance, metadata override, coercion, value priority, or observing property changes via GetObservable or OnPropertyChanged.

The file

We split this skill into the rules it gives an agent

1 rule. About 0 are common, 1 is distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use for properties that back observable collections, streams, or are performance-sensitive:

    line 44

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Avalonia Property System

Overview

AvaloniaProperty is the foundation of Avalonia's data binding, styling, and animations. Three kinds:

  • StyledProperty — full-featured; supports styling, animation, inheritance
  • DirectProperty — lightweight, CLR-field-backed; no styling/animation support
  • AttachedProperty — set on foreign controls (like Grid.Row)

StyledProperty

public static readonly StyledProperty<IBrush?> BackgroundProperty =
    AvaloniaProperty.Register<MyControl, IBrush?>(
        name: nameof(Background),
        defaultValue: null,
        inherits: false,
        defaultBindingMode: BindingMode.OneWay,
        validate: null,
        coerce: CoerceBackground);

public IBrush? Background
{
    get => GetValue(BackgroundProperty);
    set => SetValue(BackgroundProperty, value);
}

private static IBrush? CoerceBackground(AvaloniaObject instance, IBrush? value)
    => value ?? Brushes.Transparent;

Inherit/reuse from another class:

// Re-register an existing property as owner
public static readonly StyledProperty<IBrush?> BackgroundProperty =
    Border.BackgroundProperty.AddOwner<MyPanel>();

DirectProperty

Use for properties that back observable collections, streams, or are performance-sensitive:

public static readonly DirectProperty<MyControl, string> TextProperty =
    AvaloniaProperty.RegisterDirect<MyControl, string>(
        name: nameof(Text),
        getter: o => o.Text,
        setter: (o, v) => o.Text = v,
        defaultBindingMode: BindingMode.TwoWay,
        enableDataValidation: true);

private string _text = "";
public string Text
{
    get => _text;
    set => SetAndRaise(TextProperty, ref _text, value);
}

SetAndRaise performs equality check, updates field, raises PropertyChanged.

AttachedProperty

public static class Grid
{
    public static readonly AttachedProperty<int> RowProperty =
        AvaloniaProperty.RegisterAttached<Grid, Control, int>(
            name: "Row",
            defaultValue: 0,
            inherits: false);

    public static int GetRow(Control element) => element.GetValue(RowProperty);
    public static void SetRow(Control element, int value) => element.SetValue(RowProperty, value);
}

XAML usage:

<TextBlock Grid.Row="1" Grid.Column="2"/>

Property Value Priority (highest → lowest)

PrioritySource
1Animation
2Local value (SetValue / binding at LocalValue)
3Style trigger (active pseudoclass setter)
4Template binding (BindingPriority.Template; 11.x name TemplatedParent was removed in 12)
5Style setter (non-triggered)
6Inherited value
7Default value

SetCurrentValue — sets without overriding bindings/styles (priority: LocalValue but clears on style change):

// Doesn't break two-way binding
myControl.SetCurrentValue(IsCheckedProperty, true);

Observing Property Changes

On a specific instance (reactive):

myTextBox
    .GetObservable(TextBox.TextProperty)
    .Subscribe(text => Console.WriteLine($"Text: {text}"));

// With debounce (requires System.Reactive)
myTextBox
    .GetObservable(TextBox.TextProperty)
    .Throttle(TimeSpan.FromMilliseconds(300))
    .ObserveOn(RxApp.MainThreadScheduler)
    .Subscribe(HandleTextChanged);

In a control, override OnPropertyChanged:

protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
    base.OnPropertyChanged(change);

    if (change.Property == ValueProperty)
    {
        var newValue = change.GetNewValue<int>();
        var oldValue = change.GetOldValue<int>();
        UpdateVisuals(newValue);
    }
}

Static class handler (fires for all instances):

static MyControl()
{
    ValueProperty.Changed.AddClassHandler<MyControl>((control, e) =>
        control.OnValueChanged(e));

    // Or multiple properties
    AffectsRender<MyControl>(ForegroundProperty, BackgroundProperty, PaddingProperty);
    AffectsMeasure<MyControl>(WidthProperty, HeightProperty);
    AffectsArrange<MyControl>(AlignmentProperty);
}

AffectsRender / AffectsMeasure / AffectsArrange — declare layout/render dependencies efficiently.

Inherited Properties

public static readonly StyledProperty<FlowDirection> FlowDirectionProperty =
    AvaloniaProperty.Register<Control, FlowDirection>(
        nameof(FlowDirection),
        defaultValue: FlowDirection.LeftToRight,
        inherits: true);

Child controls inherit the value from their nearest ancestor that sets it explicitly.

Metadata Override

Override default value or coerce function for a subclass:

static MyControl()
{
    BackgroundProperty.OverrideMetadata<MyControl>(
        new StyledPropertyMetadata<IBrush?>(
            defaultValue: Brushes.White,
            defaultBindingMode: BindingMode.OneWay,
            coerce: MyCoerce));
}

Data Validation

Enable on a property with enableDataValidation: true, then throw in setter or implement INotifyDataErrorInfo. (12.0+) such properties report validation errors automatically; remove UpdateDataValidation overrides that only call DataValidationErrors.SetError. To keep custom handling, override UpdateDataValidation(AvaloniaProperty, BindingValueType, Exception?) and skip the base call for that property. Data-annotations validation is opt-in in 12 (on by default in 11.x).

private int _age;
public int Age
{
    get => _age;
    set
    {
        if (value < 0 || value > 150)
            throw new ArgumentOutOfRangeException(nameof(Age), "Age must be 0–150");
        SetAndRaise(AgeProperty, ref _age, value);
    }
}

Common Mistakes

  • Field must end with Property — convention: FooProperty for public property Foo; breaks tooling otherwise
  • DirectProperty for rarely-changed values — incurs no overhead, but can't be used in styles or animations; use StyledProperty if styling needed
  • Forgetting SetAndRaise in DirectProperty setter — plain field assignment won't notify bindings
  • Setting value in constructor via property setter — creates a local value that blocks style/theme overrides; use SetCurrentValue or set field directly in constructor
  • AddOwner vs. Register — AddOwner shares metadata and identity with source property (same AvaloniaProperty instance); bindings between types sharing a property work correctly
  • (11.x) IStyleable, BindingPriority.TemplatedParent — removed in 12; use StyledElement and BindingPriority.Template
  • change.GetNewValue<T>() — must match registered type exactly; mismatched cast throws InvalidCastException

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.