xaml-csharp-development-skill-for-avalonia

Bindings, XAML, and AOT Compatibility

Core Rule

Default to precompiled XAML + compiled bindings. Treat reflection-based or runtime-loaded paths as explicit exceptions.

XAML Loading Modes

Production path

Dynamic/runtime path

Use runtime loaders only when the feature explicitly requires dynamic XAML (plugin/theme editor-like scenarios).

Binding Families

For detailed converter guidance (single-value/multi-value, XAML resources, function-based converters, and binding wiring), see:

For advanced typed/untyped binding value semantics (BindingValue<T>, BindingNotification, InstancedBinding, IndexerDescriptor), see:

For RelativeSource, StaticResource, and name-scope resolution markup (ResolveByNameExtension), see:

Advanced binding-assignment contract:

Compiled bindings (preferred)

Primary APIs:

Benefits:

Reflection bindings (fallback)

Primary APIs:

Tradeoffs:

Required XAML Patterns for Typed/Compiled Binding

  1. Set x:DataType on root views and templates.
  2. Use {CompiledBinding ...} where practical.
  3. Keep property and command names strongly aligned with viewmodel contracts.

Example:

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:vm="using:MyApp.ViewModels"
             x:Class="MyApp.Views.MainView"
             x:DataType="vm:MainViewModel">
  <StackPanel Spacing="8">
    <TextBox Text="{CompiledBinding Query, Mode=TwoWay}" />
    <Button Content="Search" Command="{CompiledBinding SearchCommand}" />
    <TextBlock Text="{CompiledBinding StatusText}" />
  </StackPanel>
</UserControl>

Compiled Binding Features to Use Deliberately

Path capabilities (through grammar/builders):

Example stream binding in XAML:

<TextBlock Text="{CompiledBinding ClockObservable^, Mode=OneWay}" />

C# Binding APIs

Bind with a BindingBase

textBlock.Bind(TextBlock.TextProperty,
    new CompiledBinding(new CompiledBindingPathBuilder()
        .Property(/* generated property info */)
        .Build()));

Bind from observables directly

IDisposable sub = textBlock.Bind(
    TextBlock.TextProperty,
    viewModel.StatusStream);

Convert observable to binding object

textBlock.Bind(TextBlock.TextProperty, viewModel.StatusStream.ToBinding());

AOT Guidance for C#-Only UI

DataTemplate and DataType

DataTemplates collections require typed templates in shared/global contexts.

Use:

Name Generator and Typed x:Name

Generator options (from Avalonia.Generators.props):

Use:

Binding Error Hygiene

Advanced Binding Result Semantics

When debugging or building advanced binding flows, use these APIs deliberately:

State mapping you should rely on:

Example: inspect typed binding stream safely.

using Avalonia;
using Avalonia.Data;

IDisposable sub = textBox
    .GetBindingObservable(TextBox.TextProperty)
    .Subscribe(v =>
    {
        switch (v.Type)
        {
            case BindingValueType.Value:
                viewModel.LastValidText = v.GetValueOrDefault() ?? string.Empty;
                break;
            case BindingValueType.DataValidationError:
            case BindingValueType.BindingError:
                viewModel.LastBindingError = v.Error?.Message;
                break;
            case BindingValueType.UnsetValue:
                // Target will fall back to the property's unbound/default behavior.
                break;
            case BindingValueType.DoNothing:
                // Preserve current target state.
                break;
        }
    });

Example: explicit source update for UpdateSourceTrigger=Explicit.

var expr = BindingOperations.GetBindingExpressionBase(textBox, TextBox.TextProperty);
expr?.UpdateSource();

Instanced and Indexer Binding APIs (Advanced)

Advanced app code occasionally needs:

Compatibility note:

These APIs are useful for dynamic binding infrastructure and diagnostics tooling, not as default app authoring style. Prefer normal XAML binding syntax unless you need dynamic composition.

Anti-Patterns

  1. Global use of {Binding ...} without x:DataType in large apps.
  2. Runtime XAML loading for normal app startup.
  3. Mixed implicit/explicit DataContext contracts with no typed binding boundary.
  4. Repeatedly switching between compiled and reflection binding styles in one view tree.

XAML-First and Code-Only Usage

Default mode:

XAML-first complete example:

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:vm="using:MyApp.ViewModels"
             x:Class="MyApp.Views.SearchView"
             x:DataType="vm:SearchViewModel">
  <StackPanel Margin="16" Spacing="8">
    <TextBox Watermark="Query"
             Text="{CompiledBinding Query, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
    <Button Content="Search" Command="{CompiledBinding SearchCommand}" />
    <TextBlock Text="{CompiledBinding StatusText}" />
  </StackPanel>
</UserControl>
using Avalonia.Controls;
using Avalonia.Markup.Xaml;

namespace MyApp.Views;

public partial class SearchView : UserControl
{
    public SearchView() => InitializeComponent();

    private void InitializeComponent() => AvaloniaXamlLoader.Load(this);
}

Code-only alternative (on request):

using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;

var panel = new StackPanel { Margin = new Thickness(16), Spacing = 8 };
var input = new TextBox { Watermark = "Query" };
input.Bind(TextBox.TextProperty, new Binding(nameof(SearchViewModel.Query))
{
    Mode = BindingMode.TwoWay,
    UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});

var run = new Button { Content = "Search" };
run.Bind(Button.CommandProperty, new Binding(nameof(SearchViewModel.SearchCommand)));

var status = new TextBlock();
status.Bind(TextBlock.TextProperty, new Binding(nameof(SearchViewModel.StatusText)));

panel.Children.Add(input);
panel.Children.Add(run);
panel.Children.Add(status);