agentleFS
linuxdevellinuxdevel/Avalonia-skills/skills/avalonia/avalonia-data-binding/SKILL.md

avalonia-data-binding

Skill50 starsChanged 7 days agoMITView on GitHub

Use when writing data bindings, compiled bindings, converters, MultiBinding, collection views, validation, or binding to commands in Avalonia. Covers binding modes, path syntax, DataContext, built-in and custom converters, FallbackValue, DataValidationException, and debugging techniques.

The file

We split this skill into the rules it gives an agent

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

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

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

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Check TraceLevel on bindings: {Binding Name, TraceLevel=Verbose}.

    line 316

≈ “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 Data Binding

Overview

(12.0+) compiled bindings are on by default (AvaloniaUseCompiledBindingsByDefault defaults true). {Binding} without x:DataType fails to compile. Compiled bindings are verified at build time and generate no reflection. Opt out per scope with x:CompileBindings="False", per binding with {ReflectionBinding}, or globally with <AvaloniaUseCompiledBindingsByDefault>false</AvaloniaUseCompiledBindingsByDefault>.

(11.x) compiled bindings are opt-in: set <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault> or x:CompileBindings="True"; otherwise {Binding} is reflection-based and {CompiledBinding} must be explicit.

Binding API in C# code

11.x12.0+
IBindingBindingBase (ReflectionBinding, CompiledBinding, TemplateBinding, IndexerBinding derive from it)
InstancedBindingBindingExpressionBase
new Binding("Path")new ReflectionBinding("Path") (Binding kept for compatibility = ReflectionBinding) or CompiledBinding
// (12.0+) typed compiled binding from a lambda
var b = CompiledBinding.Create((Item item) => item.Value);

Binding plugins (BindingPlugins) are removed in 12; data-annotations validation is off by default (11.x: on).


Binding Modes

ModeDirectionUpdates
OneWaySource → TargetOn source change
TwoWaySource ↔ TargetOn either change
OneTimeSource → TargetOnce at bind time
OneWayToSourceTarget → SourceOn target change
DefaultPer-property defaultTypically TwoWay for input controls, OneWay for display

Basic Binding Syntax

<!-- Compiled binding — requires x:DataType on scope root -->
<Window xmlns:vm="using:MyApp.ViewModels"
        x:DataType="vm:MainViewModel">
    <TextBox Text="{Binding Name}"/>
    <TextBlock Text="{Binding Name, Mode=OneWay}"/>
    <TextBlock Text="{Binding Name, StringFormat='Hello, {0}!'}"/>
</Window>

<!-- Opt out per-binding (reflection binding) -->
<TextBox Text="{Binding Name, x:CompileBindings=False}"/>

<!-- Explicit compiled binding syntax (default in 12.0+; required in 11.x unless compiled-by-default is set) -->
<TextBox Text="{CompiledBinding Name}"/>

Binding Path Syntax

SyntaxMeaning
{Binding Name}Property on DataContext
{Binding Address.Street}Nested property path
{Binding [0]}Indexer
{Binding [key]}Dictionary indexer
{Binding .}DataContext itself
{Binding #controlName.Property}Named control in same scope
{Binding $parent.Property}Nearest parent control
{Binding $parent[Window].Property}Nearest parent of type Window
{Binding $parent[1].Property}Parent N levels up
{Binding $self.Property}The control itself
{Binding $root.Property}Root of logical tree

DataContext

Inherits down the logical tree. Child controls automatically receive the parent DataContext unless overridden.

<!-- Set in XAML -->
<Window DataContext="{x:Static vm:DesignData.Main}"
        x:DataType="vm:MainViewModel">

<!-- Design-time only data context -->
<Design.DataContext>
    <vm:MainViewModelDesign/>
</Design.DataContext>
// Set in code-behind
public MainWindow()
{
    InitializeComponent();
    DataContext = new MainViewModel();
}

x:DataType informs the compiler what type the DataContext is. Required for every scope (Window, UserControl, DataTemplate) that uses compiled bindings.


Converters

Built-in (namespace Avalonia.Data.Converters)

ConverterUsage
BooleanToVisibilityConverterbool → IsVisible (rarely needed; bind IsVisible to bool directly)
StringConverters.IsNullOrEmptystring → bool
StringConverters.IsNotNullOrEmptystring → bool
ObjectConverters.IsNullobject → bool
ObjectConverters.IsNotNullobject → bool
FuncValueConverter<TIn,TOut>Inline lambda converter
<TextBlock IsVisible="{Binding Name, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>

FuncValueConverter (inline, no class needed)

// In ViewModel or resource file
public static readonly FuncValueConverter<int, string> CountToLabel =
    new(count => count == 1 ? "item" : "items");
<TextBlock Text="{Binding Count, Converter={x:Static vm:Converters.CountToLabel}}"/>

Custom IValueConverter

public class InverseBoolConverter : IValueConverter
{
    public static readonly InverseBoolConverter Instance = new();

    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
        => value is bool b ? !b : BindingOperations.DoNothing;

    public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
        => Convert(value, targetType, parameter, culture);
}
<!-- Register as resource -->
<Window.Resources>
    <converters:InverseBoolConverter x:Key="InverseBool"/>
</Window.Resources>

<Button IsEnabled="{Binding IsBusy, Converter={StaticResource InverseBool}}"/>

Converter with Parameter

<TextBlock Text="{Binding Price, Converter={StaticResource CurrencyConverter}, ConverterParameter=USD}"/>

MultiBinding

<TextBlock>
    <TextBlock.Text>
        <MultiBinding StringFormat="{}{0} {1}">
            <Binding Path="FirstName"/>
            <Binding Path="LastName"/>
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

FuncMultiValueConverter<TIn,TOut> lambda parameter is IReadOnlyList<TIn> in 12.0+ (IEnumerable<TIn> in 11.x) — indexable (values[0]); update lambdas that declare IEnumerable<TIn>.

With a custom IMultiValueConverter:

public class FullNameConverter : IMultiValueConverter
{
    public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
        => values is [string first, string last] ? $"{first} {last}" : null;
}

FallbackValue and TargetNullValue

<!-- TargetNullValue: shown when binding value is null -->
<TextBlock Text="{Binding Description, TargetNullValue='No description'}"/>

<!-- FallbackValue: shown when binding fails entirely (path missing, exception) -->
<Image Source="{Binding AvatarUrl, FallbackValue={StaticResource DefaultAvatar}}"/>

Binding to Collections

<ListBox ItemsSource="{Binding Items}"/>
<DataGrid ItemsSource="{Binding Items}"/>

For sorting and filtering, use DataGridCollectionView:

var view = new DataGridCollectionView(myObservableList);
view.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
view.Filter = item => ((MyItem)item).IsActive;
DataContext = new { Items = view };

For reactive filtering without DataGridCollectionView, use a computed IEnumerable property that re-exposes a filtered collection and raise PropertyChanged when the filter changes.


Binding Validation

Throw DataValidationException from the property setter:

private string _name = "";
public string Name
{
    get => _name;
    set
    {
        if (string.IsNullOrWhiteSpace(value))
            throw new DataValidationException("Name cannot be empty.");
        SetAndRaise(NameProperty, ref _name, value); // AvaloniaObject
        // or for INPC:
        _name = value;
        OnPropertyChanged();
    }
}

Data-annotations validation is off by default in 12.0+ (on in 11.x). Exceptions thrown in setters (DataValidationException) and INotifyDataErrorInfo still surface. For AvaloniaObject DirectProperty, enableDataValidation: true makes the property report errors automatically (12.0+):

public static readonly DirectProperty<MyControl, string> NameProperty =
    AvaloniaProperty.RegisterDirect<MyControl, string>(
        nameof(Name), o => o.Name, (o, v) => o.Name = v,
        enableDataValidation: true);

Display validation errors with the built-in control:

<DataValidationErrors/>

Or access errors via the attached property DataValidationErrors.GetErrors(control).


Binding to Commands

<Button Command="{Binding SaveCommand}"/>
<Button Command="{Binding DeleteCommand}" CommandParameter="{Binding SelectedItem}"/>

<!-- Binding to a method directly (ReactiveUI / CommunityToolkit) -->
<Button Command="{Binding DoSomething}"/>

With IRelayCommand (CommunityToolkit.Mvvm):

[RelayCommand]
private async Task SaveAsync()
{
    // ...
}
// Generates SaveCommand property automatically

With ReactiveCommand (ReactiveUI):

public ICommand SaveCommand { get; }

SaveCommand = ReactiveCommand.CreateFromTask(SaveAsync, canSave);

Binding in ItemTemplate (DataTemplate scope)

Each item in a collection gets its own DataContext. Declare x:DataType on the DataTemplate:

<ListBox ItemsSource="{Binding People}">
    <ListBox.ItemTemplate>
        <DataTemplate x:DataType="vm:PersonViewModel">
            <TextBlock Text="{Binding FullName}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Binding Debugging

<!-- Enable trace output for a specific binding -->
<TextBlock Text="{Binding Name, diag:Binding.DebugMode=True}"
           xmlns:diag="clr-namespace:Avalonia.Diagnostics;assembly=Avalonia"/>
  • DevTools (F12 in Debug builds) — (12.0+) AvaloniaUI.DiagnosticsSupport package + this.AttachDeveloperTools() in App; (11.x) Avalonia.Diagnostics package + this.AttachDevTools() (package does not exist for 12). Data Context panel shows current DataContext hierarchy and property values.
  • Output window: Binding errors appear as warnings, e.g. [Binding] Could not find a matching property accessor for 'Name'.
  • Check TraceLevel on bindings: {Binding Name, TraceLevel=Verbose}.

Common Mistakes

  • Missing x:DataType — compiled bindings fail at build time with a type-resolution error. Every scope (Window, UserControl, DataTemplate) that uses compiled bindings needs x:DataType.
  • Binding to non-public members — compiled bindings require public properties. Internal or private members cause build errors.
  • Not implementing INotifyPropertyChanged — binding updates are silently dropped. Use ObservableObject (CommunityToolkit), ReactiveObject (ReactiveUI), or AvaloniaObject with SetAndRaise.
  • Using OneTime expecting updates — OneTime captures the value once at binding activation. Use OneWay for live updates.
  • Writing new Binding(...) / IBinding in 12 code — use ReflectionBinding/CompiledBinding and BindingBase.
  • Relying on BindingPlugins or implicit annotation validation in 12 — removed / off by default.
  • Mixing {Binding} and {CompiledBinding} carelessly — in Avalonia 12, {Binding} is compiled by default (in 11.x it is reflection unless configured). Opt out explicitly with x:CompileBindings=False on the element, not by switching to {CompiledBinding} (they are the same thing).
  • Reflection binding path with compiled binding scope — special paths like $parent, $self, #name work only in compiled bindings and require the target type to be resolvable.
  • Binding Command to a method name, not a property — Command must bind to an ICommand property. Methods need to be wrapped in a command object.

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.