Learn-skills.dev avalonia-data-binding
Comprehensive guide to data binding patterns in Avalonia for AI agents.
install
source · Clone the upstream repo
git clone https://github.com/NeverSight/learn-skills.dev
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/abdssamie/quater/avalonia-data-binding" ~/.claude/skills/neversight-learn-skills-dev-avalonia-data-binding && rm -rf "$T"
manifest:
data/skills-md/abdssamie/quater/avalonia-data-binding/SKILL.mdsource content
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
<Button Content="Save" Command="{Binding SaveCommand}" /> <Button Content="Delete" Command="{Binding DeleteCommand}" CommandParameter="{Binding SelectedItem}" />```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
- Always set x:DataType for compiled bindings
- Use TwoWay explicitly for input controls
- **Use OneTimetic data to improve performance
- Prefer computed properties over complex converters
- Use StringFormat for simple formatting
- Use converters for complex transformations
- Bind to commands instead of event handlers
- Use ObservableCollection for dynamic lists
- Implement INotifyPropertyChanged (via CommunityToolkit.Mvvm)
- 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