Avalonia UI Framework - Complete API & Best Practices Guide
Target Framework: .NET 10.0+
File Extension: .axaml (Avalonia XAML)
Official Docs: https://docs.avaloniaui.net/
Table of Contents
-
AXAML Fundamentals
-
Controls & UI Elements
-
Layout System
-
Data Binding
-
MVVM Pattern with ReactiveUI
-
Styling & Theming
-
Dependency & Attached Properties
-
Custom Controls
-
Control Templates
-
Resources & Converters
-
Events & Commands
-
Cross-Platform Patterns
-
Performance & Best Practices
-
Common Patterns in XerahS
AXAML Fundamentals
File Structure
Every .axaml file follows this standard structure:
<Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="using:YourApp.ViewModels" x:Class="YourApp.Views.MainWindow" x:DataType="vm:MainViewModel" x:CompileBindings="True">
<!-- Content here -->
</Window>
Required Namespace Declarations
Namespace Purpose Required
xmlns="https://github.com/avaloniaui"
Core Avalonia controls ✅ Always
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
XAML language features ✅ Always
xmlns:vm="using:YourNamespace.ViewModels"
ViewModel references ⚠️ For MVVM
xmlns:local="using:YourNamespace"
Local types/controls 🔹 As needed
Custom Namespace Syntax
<!-- Current assembly --> <xmlns:myAlias1="using:AppNameSpace.MyNamespace">
<!-- Referenced assembly (library) --> <xmlns:myAlias2="clr-namespace:OtherAssembly.MyNameSpace;assembly=OtherAssembly">
<!-- Alternative using: prefix (Avalonia style) --> <xmlns:controls="using:XerahS.UI.Controls">
Control Content vs. Attributes
<!-- Using Content property (implicit) --> <Button>Hello World!</Button>
<!-- Using Content attribute (explicit) --> <Button Content="Hello World!" />
<!-- Using property element syntax --> <Button> <Button.Content> <StackPanel> <TextBlock Text="Complex" /> <TextBlock Text="Content" /> </StackPanel> </Button.Content> </Button>
Controls & UI Elements
Common Built-in Controls
Input Controls
-
TextBox: Single/multi-line text input
-
PasswordBox: Masked password input
-
NumericUpDown: Numeric input with increment/decrement
-
CheckBox: Boolean toggle
-
RadioButton: Mutually exclusive selection
-
Slider: Continuous range selection
-
ComboBox: Dropdown selection
-
AutoCompleteBox: Text input with suggestions
-
DatePicker: Date selection
-
TimePicker: Time selection
-
ColorPicker: Color selection
Display Controls
-
TextBlock: Read-only text display
-
Label: Text with access key support
-
Image: Display images
-
Border: Visual border around content
-
ContentControl: Single content container
Layout Panels
-
Panel: Basic container (fills available space)
-
StackPanel: Vertical/horizontal stack
-
Grid: Row/column grid layout
-
DockPanel: Edge-docked layout
-
Canvas: Absolute positioning
-
WrapPanel: Wrapping flow layout
-
RelativePanel: Relative positioning
-
UniformGrid: Equal-sized cells
Lists & Collections
-
ListBox: Selectable list
-
ListView: List with view customization
-
TreeView: Hierarchical tree
-
DataGrid: Tabular data with columns
-
ItemsControl: Base collection display
-
ItemsRepeater: Virtualizing collection
Containers
-
Window: Top-level window
-
UserControl: Reusable UI component
-
ScrollViewer: Scrollable content
-
Expander: Collapsible content
-
TabControl: Tabbed interface
-
SplitView: Hamburger menu pattern
Buttons
-
Button: Standard button
-
ToggleButton: Two-state button
-
RepeatButton: Auto-repeating button
-
RadioButton: Mutually exclusive button
-
SplitButton: Button with dropdown
-
DropDownButton: Dropdown menu button
Advanced
-
Carousel: Cycling content display
-
MenuFlyout: Modern flyout-based context menu (⚠️ Use this with FluentAvalonia)
-
ContextFlyout: Right-click menu container (⚠️ Preferred over ContextMenu)
-
ContextMenu: Legacy right-click menu (⚠️ Avoid with FluentAvalonia theme)
-
Menu: Menu bar
-
ToolTip: Hover information
-
Flyout: Popup overlay
-
Calendar: Calendar display
Layout System
Layout Process
Avalonia uses a two-pass layout system:
-
Measure Pass: Determines desired size of each control
-
Arrange Pass: Positions controls within available space
Control → Measure → MeasureOverride → DesiredSize → Arrange → ArrangeOverride → FinalSize
Panel Comparison
Panel Use Case Performance Complexity
Panel Fill available space ⚡ Best Simple
StackPanel Linear stack ⚡ Good Simple
Canvas Absolute positioning ⚡ Good Simple
DockPanel Edge docking ✅ Good Medium
Grid Complex layouts ⚠️ Moderate Complex
RelativePanel Relative constraints ⚠️ Moderate Complex
Recommendation: Use Panel instead of Grid with no rows/columns for better performance.
Common Layout Properties
<Control Width="100" <!-- Fixed width --> Height="50" <!-- Fixed height --> MinWidth="50" <!-- Minimum width --> MaxWidth="200" <!-- Maximum width --> Margin="10,5,10,5" <!-- Left,Top,Right,Bottom --> Padding="5" <!-- Uniform padding --> HorizontalAlignment="Stretch" <!-- Left|Center|Right|Stretch --> VerticalAlignment="Center" <!-- Top|Center|Bottom|Stretch --> HorizontalContentAlignment="Center" <!-- For content within --> VerticalContentAlignment="Center" />
Grid Layout
<Grid RowDefinitions="Auto,,50" <!-- Rows: auto-size, fill, fixed 50 --> ColumnDefinitions="200,,Auto"> <!-- Cols: 200, fill, auto-size -->
<TextBlock Grid.Row="0" Grid.Column="0" Text="Header" />
<Border Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" />
<!-- Star sizing for proportions -->
<Grid ColumnDefinitions="*,2*,*"> <!-- 1:2:1 ratio -->
<!-- ... -->
</Grid>
</Grid>
DockPanel Layout
<DockPanel LastChildFill="True"> <Menu DockPanel.Dock="Top" /> <StatusBar DockPanel.Dock="Bottom" /> <TreeView DockPanel.Dock="Left" Width="200" />
<!-- Last child fills remaining space -->
<ContentControl Content="{Binding CurrentView}" />
</DockPanel>
StackPanel Layout
<StackPanel Orientation="Vertical" <!-- Vertical|Horizontal --> Spacing="10"> <!-- Space between items --> <TextBlock Text="Item 1" /> <TextBlock Text="Item 2" /> <TextBlock Text="Item 3" /> </StackPanel>
Data Binding
Binding Syntax
<!-- Basic binding --> <TextBlock Text="{Binding PropertyName}" />
<!-- Binding with path --> <TextBlock Text="{Binding Person.Name}" />
<!-- Binding modes --> <TextBox Text="{Binding Name, Mode=TwoWay}" /> <!-- Modes: OneWay (default), TwoWay, OneTime, OneWayToSource -->
<!-- Binding to named element --> <TextBlock x:Name="MyText" Text="Hello" /> <TextBox Text="{Binding #MyText.Text}" />
<!-- Binding to parent DataContext --> <TextBlock Text="{Binding $parent[Window].DataContext.Title}" />
<!-- Binding with fallback --> <TextBlock Text="{Binding Name, FallbackValue='Unknown'}" />
<!-- Binding with string format --> <TextBlock Text="{Binding Price, StringFormat='${0:F2}'}" />
<!-- Binding with converter --> <TextBlock Text="{Binding IsEnabled, Converter={StaticResource BoolToStringConverter}}" />
Compiled Bindings (Recommended)
Compiled bindings provide compile-time safety and better performance.
<!-- Enable compiled bindings globally in .csproj --> <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<!-- Enable for specific view --> <Window x:DataType="vm:MainViewModel" x:CompileBindings="True">
<!-- Type-safe binding -->
<TextBox Text="{Binding FirstName}" />
<TextBox Text="{Binding LastName}" />
<!-- Disable for specific binding -->
<Button Command="{ReflectionBinding OldCommand}" />
</Window>
<!-- Or use CompiledBinding markup explicitly --> <TextBox Text="{CompiledBinding FirstName}" />
Best Practice: Always use compiled bindings for type safety and performance.
DataContext Type Inference (v11.3+)
<Window x:Name="MyWindow" x:DataType="vm:TestDataContext">
<!-- Compiler infers DataContext type automatically -->
<TextBlock Text="{Binding #MyWindow.DataContext.StringProperty}" />
<TextBlock Text="{Binding $parent[Window].DataContext.StringProperty}" />
<!-- No explicit type casting needed! -->
</Window>
Multi-Binding
<TextBlock> <TextBlock.Text> <MultiBinding StringFormat="{}{0} {1}"> <Binding Path="FirstName" /> <Binding Path="LastName" /> </MultiBinding> </TextBlock.Text> </TextBlock>
Element Binding
<!-- Bind to another element's property --> <Slider x:Name="volumeSlider" Minimum="0" Maximum="100" Value="50" /> <TextBlock Text="{Binding #volumeSlider.Value}" />
<!-- Bind to parent control --> <Border BorderThickness="{Binding $parent.IsMouseOver, Converter={StaticResource BoolToThicknessConverter}}" />
MVVM Pattern with ReactiveUI
Setup
<!-- App.axaml.cs --> public override void Initialize() { AvaloniaXamlLoader.Load(this); }
<!-- Program.cs --> public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure<App>() .UsePlatformDetect() .LogToTrace() .UseReactiveUI(); // ← Enable ReactiveUI
Install Package
dotnet add package ReactiveUI.Avalonia
ViewModel Base Pattern
using ReactiveUI; using System.Reactive;
public class MainViewModel : ReactiveObject { private string _firstName = string.Empty; public string FirstName { get => _firstName; set => this.RaiseAndSetIfChanged(ref _firstName, value); }
private bool _isLoading;
public bool IsLoading
{
get => _isLoading;
set => this.RaiseAndSetIfChanged(ref _isLoading, value);
}
// Reactive command
public ReactiveCommand<Unit, Unit> SaveCommand { get; }
public MainViewModel()
{
// Command with validation
var canSave = this.WhenAnyValue(
x => x.FirstName,
x => !string.IsNullOrWhiteSpace(x));
SaveCommand = ReactiveCommand.CreateFromTask(
SaveAsync,
canSave);
}
private async Task SaveAsync()
{
IsLoading = true;
try
{
await Task.Delay(1000); // Simulate save
}
finally
{
IsLoading = false;
}
}
}
View Activation
public partial class MainView : ReactiveUserControl<MainViewModel> { public MainView() { InitializeComponent();
this.WhenActivated(disposables =>
{
// Bind ViewModel properties to View
this.Bind(ViewModel,
vm => vm.FirstName,
v => v.FirstNameTextBox.Text)
.DisposeWith(disposables);
// One-way binding
this.OneWayBind(ViewModel,
vm => vm.IsLoading,
v => v.LoadingSpinner.IsVisible)
.DisposeWith(disposables);
// Bind commands
this.BindCommand(ViewModel,
vm => vm.SaveCommand,
v => v.SaveButton)
.DisposeWith(disposables);
});
}
}
Styling & Theming
Style Types
Avalonia has three styling mechanisms:
-
Styles: Similar to CSS, target controls by type or class
-
Control Themes: Complete visual templates (like WPF Styles)
-
Container Queries: Responsive styles based on container size
Basic Styles
<Window.Styles> <!-- Style by Type --> <Style Selector="TextBlock"> <Setter Property="Foreground" Value="White" /> <Setter Property="FontSize" Value="14" /> </Style>
<!-- Style by Class -->
<Style Selector="TextBlock.header">
<Setter Property="FontSize" Value="24" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<!-- Style by property -->
<Style Selector="Button:pointerover">
<Setter Property="Background" Value="LightBlue" />
</Style>
<!-- Nested selectors -->
<Style Selector="StackPanel > Button">
<Setter Property="Margin" Value="5" />
</Style>
</Window.Styles>
<!-- Apply class --> <TextBlock Classes="header" Text="Title" />
Pseudo-classes
<Style Selector="Button:pointerover"> <!-- Mouse hover --> <Style Selector="Button:pressed"> <!-- Mouse down --> <Style Selector="Button:disabled"> <!-- Disabled state --> <Style Selector="ListBoxItem:selected"> <!-- Selected item --> <Style Selector="TextBox:focus"> <!-- Keyboard focus --> <Style Selector="CheckBox:checked"> <!-- Checked state --> <Style Selector="ToggleButton:unchecked"> <!-- Unchecked state -->
Style Selectors
<!-- Descendant (any depth) --> <Style Selector="StackPanel TextBlock">
<!-- Direct child --> <Style Selector="StackPanel > TextBlock">
<!-- Multiple conditions (AND) --> <Style Selector="Button.primary:pointerover">
<!-- Multiple selectors (OR) --> <Style Selector="Button, ToggleButton">
<!-- Negation --> <Style Selector="Button:not(.primary)">
<!-- Template parts --> <Style Selector="Button /template/ ContentPresenter">
Resources
<Window.Resources> <!-- Solid color brush --> <SolidColorBrush x:Key="PrimaryBrush" Color="#007ACC" />
<!-- Static resource -->
<x:Double x:Key="StandardSpacing">10</x:Double>
<!-- Gradient brush -->
<LinearGradientBrush x:Key="GradientBrush" StartPoint="0%,0%" EndPoint="0%,100%">
<GradientStop Color="#FF0000" Offset="0" />
<GradientStop Color="#00FF00" Offset="1" />
</LinearGradientBrush>
</Window.Resources>
<!-- Use resources --> <Button Background="{StaticResource PrimaryBrush}" Margin="{StaticResource StandardSpacing}" />
<!-- DynamicResource (updates when changed) --> <Button Background="{DynamicResource PrimaryBrush}" />
Themes
<!-- App.axaml --> <Application.Styles> <!-- FluentTheme (Windows 11 style) --> <FluentTheme />
<!-- Or Simple theme -->
<SimpleTheme />
<!-- Custom styles -->
<StyleInclude Source="/Styles/CustomStyles.axaml" />
</Application.Styles>
Dependency & Attached Properties
StyledProperty (Dependency Property)
public class MyControl : ContentControl { // Define the property public static readonly StyledProperty<string> TitleProperty = AvaloniaProperty.Register<MyControl, string>( nameof(Title), defaultValue: string.Empty);
// CLR wrapper
public string Title
{
get => GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
// React to property changes
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == TitleProperty)
{
// Handle change
var oldValue = (string?)change.OldValue;
var newValue = (string?)change.NewValue;
}
}
}
Attached Properties
public class MyPanel : Panel { // Define attached property public static readonly AttachedProperty<int> ColumnProperty = AvaloniaProperty.RegisterAttached<MyPanel, Control, int>( "Column", defaultValue: 0);
// Getters/Setters
public static int GetColumn(Control element)
=> element.GetValue(ColumnProperty);
public static void SetColumn(Control element, int value)
=> element.SetValue(ColumnProperty, value);
}
<!-- Use attached property --> <local:MyPanel> <Button local:MyPanel.Column="0" Content="First" /> <Button local:MyPanel.Column="1" Content="Second" /> </local:MyPanel>
Common Attached Properties
<!-- Grid --> <Button Grid.Row="0" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="3" />
<!-- DockPanel --> <Menu DockPanel.Dock="Top" />
<!-- Canvas --> <Rectangle Canvas.Left="50" Canvas.Top="100" />
<!-- ToolTip --> <Button ToolTip.Tip="Click me!" />
<!-- ContextFlyout (Preferred with FluentAvalonia) --> <Border> <Border.ContextFlyout> <MenuFlyout> <MenuItem Header="Copy" /> <MenuItem Header="Paste" /> </MenuFlyout> </Border.ContextFlyout> </Border>
⚠️ XerahS-Specific: ContextMenu vs ContextFlyout
Critical Issue with FluentAvalonia Theme
Problem: Standard ContextMenu controls do not render correctly with FluentAvaloniaTheme . They use legacy Popup windows which are not fully styled and may appear unstyled or invisible.
Solution: ✅ Always use ContextFlyout with MenuFlyout instead of ContextMenu .
<!-- ❌ INCORRECT: May be invisible with FluentAvalonia --> <Border.ContextMenu> <ContextMenu> <MenuItem Header="Action" Command="{Binding MyCommand}"/> </ContextMenu> </Border.ContextMenu>
<!-- ✅ CORRECT: Use ContextFlyout with MenuFlyout --> <Border.ContextFlyout> <MenuFlyout> <MenuItem Header="Action" Command="{Binding MyCommand}"/> </MenuFlyout> </Border.ContextFlyout>
Binding in DataTemplates with Flyouts
Problem: When using ContextFlyout or ContextMenu inside a DataTemplate , bindings to the parent ViewModel fail because Popups/Flyouts exist in a separate visual tree, detached from the DataTemplate's hierarchy.
Solution: Use $parent[UserControl].DataContext to reach the main view's DataContext.
<DataTemplate x:DataType="local:MyItem"> <Border> <Border.ContextFlyout> <MenuFlyout> <!-- ✅ Bind to parent UserControl's DataContext --> <MenuItem Header="Edit" Command="{Binding $parent[UserControl].DataContext.EditCommand}" CommandParameter="{Binding}"/> </MenuFlyout> </Border.ContextFlyout>
<TextBlock Text="{Binding Name}" />
</Border>
</DataTemplate>
Key Points:
-
Use $parent[UserControl].DataContext to access the View's ViewModel from within a flyout
-
CommandParameter="{Binding}" passes the current data item (the DataTemplate's DataContext)
-
For shared flyouts, define them in UserControl.Resources and reference via {StaticResource}
Custom Controls
Custom Control (Draws itself)
public class CircleControl : Control { public static readonly StyledProperty<IBrush?> FillProperty = AvaloniaProperty.Register<CircleControl, IBrush?>(nameof(Fill));
public IBrush? Fill
{
get => GetValue(FillProperty);
set => SetValue(FillProperty, value);
}
public override void Render(DrawingContext context)
{
var renderSize = Bounds.Size;
var center = new Point(renderSize.Width / 2, renderSize.Height / 2);
var radius = Math.Min(renderSize.Width, renderSize.Height) / 2;
context.DrawEllipse(Fill, null, center, radius, radius);
}
}
Templated Control (Look-less)
public class MyButton : TemplatedControl { public static readonly StyledProperty<string> TextProperty = AvaloniaProperty.Register<MyButton, string>(nameof(Text));
public string Text
{
get => GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
// Find template parts
var presenter = e.NameScope.Find<ContentPresenter>("PART_ContentPresenter");
}
}
UserControl (Composite)
<!-- MyUserControl.axaml --> <UserControl xmlns="https://github.com/avaloniaui" x:Class="MyApp.Controls.MyUserControl"> <StackPanel> <TextBlock Text="{Binding Title}" /> <Button Content="Click Me" /> </StackPanel> </UserControl>
// MyUserControl.axaml.cs public partial class MyUserControl : UserControl { public MyUserControl() { InitializeComponent(); } }
Control Templates
Define a ControlTheme
<ControlTheme x:Key="CustomButtonTheme" TargetType="Button"> <Setter Property="Background" Value="Blue" /> <Setter Property="Foreground" Value="White" /> <Setter Property="Padding" Value="10,5" /> <Setter Property="Template"> <ControlTemplate> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="5"> <ContentPresenter Name="PART_ContentPresenter" Content="{TemplateBinding Content}" Padding="{TemplateBinding Padding}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" /> </Border> </ControlTemplate> </Setter>
<!-- Pseudo-class styles -->
<Style Selector="^:pointerover /template/ Border">
<Setter Property="Background" Value="LightBlue" />
</Style>
<Style Selector="^:pressed /template/ Border">
<Setter Property="Background" Value="DarkBlue" />
</Style>
</ControlTheme>
<!-- Apply theme --> <Button Theme="{StaticResource CustomButtonTheme}" Content="Custom" />
Template Parts
[TemplatePart("PART_ContentPresenter", typeof(ContentPresenter))] public class MyTemplatedControl : TemplatedControl { private ContentPresenter? _presenter;
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_presenter = e.NameScope.Find<ContentPresenter>("PART_ContentPresenter");
}
}
Resources & Converters
Value Converters
public class BoolToVisibilityConverter : IValueConverter { public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is bool boolValue) return boolValue ? true : false; // Or specific logic return false; }
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is bool visible)
return visible;
return false;
}
}
<Window.Resources> <local:BoolToVisibilityConverter x:Key="BoolToVisConverter" /> </Window.Resources>
<Border IsVisible="{Binding IsActive, Converter={StaticResource BoolToVisConverter}}" />
Built-in Converters
<!-- Negation --> <Button IsEnabled="{Binding !IsLoading}" />
<!-- Null check --> <TextBlock IsVisible="{Binding MyObject, Converter={x:Static ObjectConverters.IsNotNull}}" />
<!-- String format --> <TextBlock Text="{Binding Count, StringFormat='Items: {0}'}" />
Events & Commands
Event Handlers
<Button Click="OnButtonClick" Content="Click" />
private void OnButtonClick(object? sender, RoutedEventArgs e) { // Handle event }
Commands (MVVM)
<Button Command="{Binding SaveCommand}" CommandParameter="{Binding CurrentItem}" Content="Save" />
// Using ReactiveUI public ReactiveCommand<object?, Unit> SaveCommand { get; }
public MyViewModel() { SaveCommand = ReactiveCommand.Create<object?>(Save); }
private void Save(object? parameter) { // Execute command }
Routed Events
public static readonly RoutedEvent<RoutedEventArgs> MyEvent = RoutedEvent.Register<MyControl, RoutedEventArgs>( nameof(MyEvent), RoutingStrategies.Bubble);
public event EventHandler<RoutedEventArgs> MyEvent { add => AddHandler(MyEvent, value); remove => RemoveHandler(MyEvent, value); }
// Raise event RaiseEvent(new RoutedEventArgs(MyEvent));
Cross-Platform Patterns
Platform Detection
if (OperatingSystem.IsWindows()) { // Windows-specific code } else if (OperatingSystem.IsMacOS()) { // macOS-specific code } else if (OperatingSystem.IsLinux()) { // Linux-specific code }
Platform-Specific Resources
<Application.Styles> <StyleInclude Source="/Styles/Common.axaml" />
<!-- Conditionally include styles -->
<OnPlatform>
<On Options="Windows">
<StyleInclude Source="/Styles/Windows.axaml" />
</On>
<On Options="macOS">
<StyleInclude Source="/Styles/macOS.axaml" />
</On>
</OnPlatform>
</Application.Styles>
Design Principles
-
Use .NET Standard: Write business logic in .NET Standard libraries
-
MVVM Pattern: Separate UI from logic
-
Avalonia Drawing: Leverage Avalonia's drawn UI (not native controls)
-
Platform Abstractions: Use interfaces for platform-specific features
-
Responsive Design: Use container queries and adaptive layouts
Performance & Best Practices
Performance Tips
-
Use Panel over Grid when no rows/columns needed
-
Enable compiled bindings globally
-
Use virtualization for large lists (ItemsRepeater , VirtualizingStackPanel )
-
Avoid deep nesting of visual trees
-
Use RenderTransform instead of Margin for animations
-
Recycle DataTemplates with ItemsRepeater
-
Minimize layout passes by batching property changes
Memory Management
// Dispose subscriptions properly this.WhenActivated(disposables => { ViewModel .WhenAnyValue(x => x.Property) .Subscribe(value => { }) .DisposeWith(disposables); });
Null Safety
XerahS uses strict nullable reference types. Always:
// Enable in .csproj <Nullable>enable</Nullable>
// Handle nullability properly public string? Title { get; set; } // Nullable public string Name { get; set; } = string.Empty; // Non-nullable with default
Common Patterns in XerahS
StyledProperty Pattern
public static readonly StyledProperty<object?> SelectedObjectProperty = AvaloniaProperty.Register<PropertyGrid, object?>(nameof(SelectedObject));
public object? SelectedObject { get => GetValue(SelectedObjectProperty); set => SetValue(SelectedObjectProperty, value); }
Attached Property Pattern (Auditing)
public static readonly AttachedProperty<bool> IsUnwiredProperty = AvaloniaProperty.RegisterAttached<UiAudit, Control, bool>("IsUnwired");
public static bool GetIsUnwired(Control control) => control.GetValue(IsUnwiredProperty);
public static void SetIsUnwired(Control control, bool value) => control.SetValue(IsUnwiredProperty, value);
Window Structure
<Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="using:XerahS.ViewModels" x:Class="XerahS.UI.Views.MainWindow" x:DataType="vm:MainViewModel" x:CompileBindings="True" Title="{Binding Title}" Width="1000" Height="700">
<Window.Styles>
<!-- Local styles -->
</Window.Styles>
<DockPanel>
<!-- Layout content -->
</DockPanel>
</Window>
Quick Reference Tables
Alignment Values
Property Values Default
HorizontalAlignment
Left , Center , Right , Stretch
Stretch
VerticalAlignment
Top , Center , Bottom , Stretch
Stretch
Binding Modes
Mode Direction Updates
OneWay
Source → Target Source changes
TwoWay
Source ↔ Target Both changes
OneTime
Source → Target Once at init
OneWayToSource
Source ← Target Target changes
Grid Sizing
Type Syntax Behavior
Auto Auto
Size to content
Pixel 100
Fixed size
Star * or 2*
Proportional fill
Additional Resources
-
Official Docs: https://docs.avaloniaui.net/
-
Samples: https://github.com/AvaloniaUI/Avalonia/tree/master/samples
-
ReactiveUI: https://reactiveui.net/
-
Community: https://avaloniaui.community/
Checklist for New Controls/Views
-
Use .axaml file extension
-
Set x:Class attribute
-
Set x:DataType for compiled bindings
-
Set x:CompileBindings="True" (or global setting)
-
Define proper namespaces
-
Use StyledProperty for custom properties
-
Follow nullable reference type rules
-
Use ReactiveUI for MVVM
-
Apply consistent styling/theming
-
⚠️ Use ContextFlyout
- MenuFlyout , NOT ContextMenu (FluentAvalonia compatibility)
-
Use $parent[UserControl].DataContext for flyout bindings in DataTemplates
-
Handle accessibility (tab order, accessible names)
-
Test on all target platforms
Last Updated: February 19, 2026
Version: 1.1.0
Maintained by: XerahS Development Team