skills/sharex/xerahs/avalonia-api

avalonia-api

SKILL.md

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

  1. AXAML Fundamentals
  2. Controls & UI Elements
  3. Layout System
  4. Data Binding
  5. MVVM Pattern with ReactiveUI
  6. Styling & Theming
  7. Dependency & Attached Properties
  8. Custom Controls
  9. Control Templates
  10. Resources & Converters
  11. Events & Commands
  12. Cross-Platform Patterns
  13. Performance & Best Practices
  14. 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:

  1. Measure Pass: Determines desired size of each control
  2. 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:

  1. Styles: Similar to CSS, target controls by type or class
  2. Control Themes: Complete visual templates (like WPF Styles)
  3. 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

  1. Use .NET Standard: Write business logic in .NET Standard libraries
  2. MVVM Pattern: Separate UI from logic
  3. Avalonia Drawing: Leverage Avalonia's drawn UI (not native controls)
  4. Platform Abstractions: Use interfaces for platform-specific features
  5. Responsive Design: Use container queries and adaptive layouts

Performance & Best Practices

Performance Tips

  1. Use Panel over Grid when no rows/columns needed
  2. Enable compiled bindings globally
  3. Use virtualization for large lists (ItemsRepeater, VirtualizingStackPanel)
  4. Avoid deep nesting of visual trees
  5. Use RenderTransform instead of Margin for animations
  6. Recycle DataTemplates with ItemsRepeater
  7. 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


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

Weekly Installs
22
Repository
sharex/xerahs
GitHub Stars
208
First Seen
Feb 27, 2026
Installed on
cline22
github-copilot22
codex22
kimi-cli22
gemini-cli22
cursor22