avalonia-data-binding

📁 abdssamie/quater 📅 Feb 11, 2026
8
总安装量
8
周安装量
#33701
全站排名
安装命令
npx skills add https://github.com/abdssamie/quater --skill avalonia-data-binding

Agent 安装分布

amp 8
gemini-cli 8
antigravity 8
github-copilot 8
codex 8
kimi-cli 8

Skill 文档

Data Binding in Avalonia

Comprehensive guide to data binding patterns in Avalonia for AI agents.

What I do

  • Guide implementation of ViewModels with ObservableObject base class
  • Show how to create observable properties with [ObservableProperty] attribute
  • Demonstrate command patterns with [RelayCommand] including async and CanExecute
  • Explain View-ViewModel mapping using DataTemplates (critical for navigation)
  • Show dependency injection patterns for passing services to ViewModels

When to use me

Use this skill when you need to:

  • Create new ViewModels for Avalonia views
  • Implement observable properties that notify the UI of changes
  • Add commands to handle user interactions (button clicks, etc.)
  • Set up navigation patterns with DataTemplates and ContentControl

Data Binding in Avalonia

Comprehensive guide to data binding patterns in Avalonia for AI agents.

Binding Modes

OneWay Binding

<!-- ViewModel property updates UI -->
<TextBlock Text="{Binding UserName}" />
<TextBlock Text="{Binding UserName, Mode=OneWay}" />  <!-- Explicit -->

TwoWay Binding

<!-- UI and ViewModel stay synchronized -->
<TextBox Text="{Binding UserName, Mode=TwoWay}" />
<Slider Value="{Binding Volume, Mode=TwoWay}" />
<CheckBox IsChecked="{Binding IsEnabled, Mode=TwoWay}" />

ViewModel:

[ObservableProperty]
private string _userName = "";

[ObservableProperty]
private double _volume = 50;

[ObservableProperty]
private bool _isEnabled = true;

OneTime Binding

<!-- Set once, never updates -->
<TextBlock Text="{Binding AppVersion, Mode=OneTime}" />
<Image Source="{Binding StaticImage, Mode=OneTime}" />

OneWayToSource Binding

<!-- Target updates source, but not vice versa -->
<TextBox Text="{Binding SearchQuery, Mode=OneWayToSource}" />

When to use each

Use:

  • OneWay: Display data that changes infrequently
  • TwoWay: User input controls (TextBox, Slider, CheckBox)
  • OneTime: Static data that won’t change after load
  • OneWayToSource: When UI updates ViewModel but not the other way

Binding Paths

Simple Property

<TextBlock Text="{Binding Name}" />
[ObservableProperty]
private string _name = "John";

Nested Property

<TextBlock Text="{Binding Person.Address.Street}" />
<TextBlock Text="{Binding User.Profile.Email}" />
[ObservableProperty]
private Person _person = new();

public class Person
{
    public Address Address { get; set; } = new();
}

public class Address
{
    public string Street { get; set; } = "";
}

Collection Indexer

<TextBlock Text="{Binding Items[0]}" />
<TextBlock Text="{Binding Users[5].Name}" />
public ObservableCollection<string> Items { get; } = new();
public ObservableCollection<User> Users { get; } = new();

Attached Property

<TextBlock Text="{Binding (Grid.Row)}" />
<TextBlock Text="{Binding (DockPanel.Dock)}" />

Current Item

<!-- Bind to current item in collection -->
<TextBlock Text="{Binding /}" />
<TextBlock Text="{Binding /Name}" />  <!-- Property of current item -->

Binding Sources

DataContext (Default)

<!-- Binds to DataContext -->
<TextBlock Text="{Binding PropertyName}" />

Element Binding

<!-- Bind to another element by name -->
<Slider x:Name="VolumeSlider" Minimum="0" Maximum="100" />
<TextBlock Text="{Binding #VolumeSlider.Value}" />

<!-- Alternative syntax -->
<TextBlock Text="{Binding ElementName=VolumeSlider, Path=Value}" />

Relative Source – Parent

<!-- Find ancestor by type -->
<TextBlock Text="{Binding $parent[Window].Title}" />
<TextBlock Text="{Binding $parent[UserControl].DataContext.PropertyName}" />
<TextBlock Text="{Binding $parent[ListBox].SelectedItem}" />

<!-- Multiple levels -->
<TextBlock Text="{Binding $parent[Grid].$parent[Window].Title}" />

Relative Source – Self

<!-- Bind to own property -->
<TextBlock Text="{Binding $self.Tag}" 
           Tag="Hello" />
<Button Content="{Binding $self.Width}" 
        Width="100" />

Static Resource

<!-- Bind to resource -->
<TextBlock Text="{StaticResource WelcomeMessage}" />
<Button Background="{DynamicResource ThemeBrush}" />

Static Property

<!-- Bind to static property -->
<TextBlock Text="{x:Static local:Constants.AppName}" />
<PathIcon Data="{x:Static icons:Icons.Home}" />

Value Converters

Built-in Converters

Boolean Converters

<!-- Not -->
<TextBlock IsVisible="{Binding IsHidden, Converter={x:Static BoolConverters.Not}}" />

<!-- And -->
<Button IsEnabled="{Binding IsValid, Converter={x:Static BoolConverters.And}, ConverterParameter={Binding IsReady}}" />

<!-- Or -->
<Control IsVisible="{Binding ShowA, Converter={x:Static BoolConverters.Or}, ConverterParameter={Binding ShowB}}" />

Object Converters

<!-- IsNull -->
<TextBlock IsVisible="{Binding Data, Converter={x:Static ObjectConverters.IsNull}}" />

<!-- IsNotNull -->
<TextBlock IsVisible="{Binding Data, Converter={x:Static ObjectConverters.IsNotNull}}" />

<!-- Equal -->
<RadioButton IsChecked="{Binding SelectedOption, Converter={x:Static ObjectConverters.Equal}, ConverterParameter=Option1}" />

Custom Converter

Define Converter:

public class BoolToColorConverter : IValueConverter
{
    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
    {
        if (value is bool boolValue)
        {
            return boolValue ? Brushes.Green : Brushes.Red;
        }
        return Brushes.Gray;
    }

    public object? ConvertBack(object? va, Type targetType, object? parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Register in Resources:

<UserControl.Resources>
    <local:BoolToColorConverter x:Key="BoolToColorConverter" />
</UserControl.Resources>

Use Converter:

<TextBlock Foreground="{Binding IsActive, Converter={StaticResource BoolToColorConverter}}" />

Converter with Parameter

<TextBlock Text="{Binding Value, 
                          Converter={StaticResource NumberToStringConverter}, 
                          ConverterParamet
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
    if (value is double number && parameter is string decimals)
    {
        return number.ToString($"F{decimals}");
    }
    return value?.ToString();
}

String Formatting

Basic Formatting

<!-- Number formatting -->
<TextBlock Text="{Binding Price, StringFormat='${0:F2}'}" />
<!-- Output: $19.99 -->

<TextBlock Text="{Binding Count, StringFormat='{}{0} items'}" />
<!-- Output: 5 items (note: {} escapes the opening brace) -->

<!-- Date formatting -->
<TextBlock Text="{Binding Date, StringForma{0:yyyy-MM-dd}'}" />
<!-- Output: Date: 2024-01-23 -->

<TextBlock Text="{Binding Time, StringFormat='{0:HH:mm:ss}'}" />
<!-- Output: 14:30:45 -->

Format Specifiers

Numeric Formats

<!-- Currency -->
<TextBlock Text="{Binding Amount, StringFormat='{}{0:C}'}" />
<!-- $1,234.56 -->

<!-- Fixed-point -->
<TextBlock Text="{Binding Value, StringFormat='{}{0:F2}'}" />
<!-- 123.46 -->

<!-- Number with separators -->
<TextBlock Text="{Binding Count, StringFormat='{}{0:N0}'}" />
<!-- 1,234 -->

<!-- Percentage -->
<TextBlock Text="{Binding Ratio, StringFormat='{}{0:P1}'}" />
<!-- 45.6% -->

<!-- Hexadecimal -->
<TextBlock Text="{Binding ColorValue, StringFormat='{}{0:X6}'}" />
<!-- FF00AA -->

Date/Time Formats

<!-- Short date -->
<TextBlock Text="{Binding Date, StringFormat='{}{0:d}'}" />
<!-- 1/23/2024 -->

<!-- Long date -->
<TextBlock Text="{Binding Date, StringFormat='{}{0:D}'}" />
<!-- Tuesday, January 23, 2024 -->

<!-- Custom date -->
<TextBlock Text="{Binding Date, StringFormat='{}{0:MMM dd, yyyy}'}" />
<!-- Jan 23, 2024 -->

<!-- Time -->
<TextBlock Text="{Binding Time, StringFormat='{}{0:t}'}" />
<!-- 2:30 PM -->

<!-- Date and time -->
<TextBlock Text="{Binding DateTime, StringFormat='{}{0:g}'}" />
<!-- 1/23/2024 2:30 PM -->

Escaping Braces

<!-- Need {} to escape opening brace -->
<TextBlock Text="{Binding Count, StringFormat='{}{0} items'}" />

<!-- Without escape (error) -->
<TextBlock Text="{Binding Count, StringFormat='{0} items'}" />  <!-- ❌ -->

Compiled Bindings

Why Compiled Bindings?

  • Performance: 2-3x faster than reflection-based bindings
  • Type Safety: Compile-time errors instead of runtime
  • IntelliSense: Better IDE support
  • Required: For Avalon with AvaloniaUseCompiledBindingsByDefault

Enabling Compiled Bindings

Project File:

<PropertyGroup>
    <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>

AXAML:

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:vm="using:YourApp.ViewModels"
             x:DataType="vm:YourViewModel">  <!-- Required! -->
    
    <TextBlock Text="{Binding PropertyName}" />  <!-- Compiled -->
</UserControl>

Compiled Binding Syntax

<!-- Standard binding (compiled if x:DataType is set) -->
<TextBlock Text="{Binding Name}" />

<!-- Explicit compiled binding -->
<TextBlock Text="{CompiledBinding Name}" />

<!-- Reflection binding (opt-out) -->
<TextBlock Text="{ReflectionBinding Name}" />

DataType for Collections

<ItemsControl ItemsSource="{Binding People}">
    <ItemsControl.ItemTemplate>
        <DataTemplate DataType="vm:PersonViewModel">
            <!-- Compiled bindings for PersonViewModel -->
            <TextBlock Text="{Binding Name}" />
            <TextBlock Text="{Binding Age}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

DataType Inheritance

<UserControl x:DataType="vm:MainViewModel">
    <!-- Binds to MainViewModel -->
    <TextBlock Text="{Binding Title}" />
    
    <ContentControl Content="{Binding ChildViewModel}">
        <ContentControl.ContentTemplate>
            <DataTemplate x:DataType="vm:ChildViewModel">
                <!-- Binds to ChildViewModel -->
                <TextBlock Text="{Binding ChildProperty}" />
            </DataTemplate>
        </ContentControl.ContentTemplate>
    </ContentControl>
</UserControl>

Common Patterns

Binding to Commands“`xml

```csharp
[RelayCommand]
private void Save()
{
    // Save logic
}

[RelayCommand]
private void Delete(object? parameter)
{
    if (parameter is Item item)
    {
        // Delete item
    }
}

Binding to Collections

<!-- ItemsControl -->
<ItemsControl ItemsSource="{Binding Items}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Name}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

<!-- ListBox with selection -->
<ListBox ItemsSource="{Binding Items}"
         SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />

<!-- DataGrid -->
<DataGrid ItemsSource="{Binding People}"
          SelectedItem="{Binding SelectedPerson, Mode=TwoWay}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
        <DataGridTextColumn Header="Age" Binding="{Binding Age}" />
    </DataGrid.Columns>
</DataGrid>
public ObservableCollection<Item> Items { get; } = new();

[ObservableProperty]
private Item? _selectedItem;

Binding to Enums

<!-- ComboBox with enum -->
<ComboBox ItemsSource="{Binding AllStatuses}"
          SelectedItem="{Binding CurrentStatus, Mode=TwoWay}" />
public enum Status { Active, Inactive, Pending }

public IEnumerable<Status> AllStatuses => Enum.GetValues<Status>();

[ObservableProperty]
private Status _currentStatus = Status.Active;

Conditional Visibility

<!-- Show/hide based on boolean -->
<TextBlock Text="Loading..." 
           IsVisible="{Binding IsLoading}" />

<!-- Show when NOT loading -->
<TextBlock Text="Content" 
           IsVisible="{Binding IsLoading, Converter={x:Static BoolConverters.Not}}" />

<!-- Show when data exists -->
<TextBlock Text="No data" 
           IsVisible="{Binding Data, Converter={x:Static ObjectConverters.IsNull}}" />

Multi-Binding (Alternative Approaches)

Option 1: Computed Property

[ObservableProperty]
private string _firstName = "";

[ObservableProperty]
private string _lastName = "";

public string FullName => $"{FirstName} {LastName}";

partial void OnFirstNameChanged(string value) => OnPropertyChanged(nameof(FullName));
partial void OnLastNameChanged(string value) => OnPropertyChanged(nameof(FullName));
<TextBlock Text="{Binding FullName}" />

Option 2: Converter

<TextBlock>
    <TextBlock.Text>
        <MultiBinding Converter="{StaticResource FullNameConverter}">
            <Binding Path="FirstName" />
            <Binding Path="LastName" />
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

Binding with Fallback

<!-- Show fallback if binding fails -->
<TextBlock Text="{Binding Name, FallbackValue='Unknown'}" />
<TextBlock Text="{Binding Age, FallbackValue=0}" />
<Image Source="{Binding ImagePath, FallbackValue='/Assets/placeholder.png'}" />

Binding with TargetNullValue

<!-- Show specific value when source is null -->
<TextBlock Text="{Binding Description, TargetNullValue='No description'}" />
<TextBlock Text="{Binding Count, TargetNullValue=0}" />

Best Practices

  1. Always set x:DataType for compiled bindings
  2. Use TwoWay explicitly for input controls
  3. **Use OneTimetic data to improve performance
  4. Prefer computed properties over complex converters
  5. Use StringFormat for simple formatting
  6. Use converters for complex transformations
  7. Bind to commands instead of event handlers
  8. Use ObservableCollection for dynamic lists
  9. Implement INotifyPropertyChanged (via CommunityToolkit.Mvvm)
  10. Test bindings with design-time data

Common Mistakes

❌ DatePicker Binding Type Mismatch

[ObservableProperty]
private DateTime _selectedDate = DateTime.Now; // ❌ InvalidCastException

✅ Correct

[ObservableProperty]
private DateTimeOffset? _selectedDate = DateTimeOffset.Now; // ✅ Use DateTimeOffset?

❌ Missing Mode for input

<TextBox Text="{Binding Name}" />  <!-- OneWay by default -->

✅ Correct

<TextBox Text="{Binding Name, Mode=TwoWay}" />

❌ Forgetting x:DataType

<UserControl x:Class="MyView">
    <TextBlock Text="{Binding Name}" />  <!-- Reflection binding -->
</UserControl>

✅ Correct

<UserControl x:Class="MyView" x:DataType="vm:MyViewModel">
    <TextBlock Text="{Binding Name}" />  <!-- Compiled binding -->
</UserControl>

❌ Not notifying property changes

private string _name;
public string Name
{
    get => _name;
    set => _name = value;  // UI won't update!
}

✅ Correct

[ObservableProperty]
private string _name = "";  // Generates proper notification