avalonia-data-binding
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
- 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