Desktop UI

XAML в Avalonia: ключові відмінності від WPF

Порівнюємо XAML синтаксис WPF та Avalonia: namespace URI, розширення .axaml, using: замість clr-namespace, ресурси та Avalonia Previewer.

XAML в Avalonia: ключові відмінності від WPF

У попередній статті ми детально розібрали XAML: namespace'и, ResourceDictionary, StaticResource та DynamicResource. Все це стосувалося WPF. Але якщо ви вже знаєте WPF XAML — ви знаєте 90% Avalonia XAML.

Залишилися 10%. Саме вони відрізняють файл MainWindow.xaml (WPF) від MainWindow.axaml (Avalonia). Ці відмінності невеликі, але якщо не знати про них — компілятор і IDE будуть лякати незрозумілими помилками на рівному місці.

Ця стаття — коротка шпаргалка: що змінилося, чому і як правильно.

Словник теми:.axaml — розширення файлів Avalonia XAML (Avalonia XAML, не WPF XAML). xmlns="https://github.com/avaloniaui" — головний namespace Avalonia, аналог довгого WPF URI. using: — скорочений синтаксис підключення CLR namespace в Avalonia (using:MyApp замість clr-namespace:MyApp). ThemeResource — Avalonia-специфічний Markup Extension для ресурсів, що реагують на зміну теми (Light/Dark). Avalonia Previewer — вбудований інструмент попереднього перегляду .axaml у Rider та VS.

Чому 90% залишається незмінним

Перш ніж перейти до відмінностей — важливо зафіксувати, що не змінюється:

КонцепціяWPFAvalonia
Синтаксис тегів<Button Content="OK"/>✅ Той самий
xmlns:x namespacehttp://schemas.microsoft.com/winfx/2006/xaml✅ Той самий
x:Name, x:Key, x:Class✅ Є✅ Є
Property Element Syntax<Button.Background>...</Button.Background>✅ Той самий
Content Property<Button>Text</Button>✅ Той самий
Type ConvertersBackground="Red" → Brush✅ Той самий
Attached PropertiesGrid.Row="1"✅ Той самий
StaticResource / DynamicResource✅ Є✅ Є
ResourceDictionary + x:Key✅ Є✅ Є
MergedDictionaries✅ Є✅ Є

Весь WPF XAML-синтаксис, що ви вивчали — валідний в Avalonia. Це не перебільшення, а факт архітектурного рішення команди Avalonia: зберегти максимальну сумісність з WPF на рівні розмітки.


Відмінність 1: .axaml замість .xaml

Перше, що помічаєш у структурі Avalonia-проєкту — всі файли розмітки мають розширення .axaml, а не .xaml.

WPF:       MainWindow.xaml  +  MainWindow.xaml.cs
Avalonia:  MainWindow.axaml +  MainWindow.axaml.cs

Чому саме .axaml

Причина суто практична — конфлікт інструментів у Visual Studio.

Коли у проєкті є файл з розширенням .xaml, Visual Studio автоматично відкриває його у WPF XAML Designer. Дизайнер намагається завантажити WPF-типи, не знаходить їх у Avalonia-збірці і падає з помилками. Це спричиняло масу плутанини у проєктах, де розробники використовували і WPF, і Avalonia.

Розширення .axaml (Avalonia XAML) не асоційоване з WPF Designer — Visual Studio відкриває його як звичайний текстовий XML-файл, а Avalonia Extension підключає власний previewer.

Технічне уточнення

.axaml — це конвенція, не технічна вимога. Avalonia чудово компілює і .xaml файли — синтаксис абсолютно однаковий. Але використання .axaml є офіційною рекомендацією команди Avalonia і це те, що генерують усі шаблони.

// .axaml.cs виглядає ідентично до .xaml.cs
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent(); // Та сама магія, що в WPF
    }
}

Відмінність 2: Namespace URI

Найпомітніша відмінність у першому рядку XAML-файлу — різний URI у xmlns:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Class="MyWpfApp.MainWindow"
        Title="WPF вікно">

Що змінилося:

  • xmlns (default namespace): WPF URI → Avalonia URI
  • xmlns:x (XAML-директиви): незмінний — той самий http://schemas.microsoft.com/winfx/2006/xaml

Avalonia URI https://github.com/avaloniaui — так само просто ідентифікатор-рядок, як і WPF URI. Він теж не завантажує нічого з GitHub. Просто більш лаконічний і легко запам'ятовується.

Що маппує Avalonia URI

Аналогічно до WPF (де XmlnsDefinition реєструвала маппінг у сборці), в Avalonia збірки також реєструють маппінг своїх namespace'ів на https://github.com/avaloniaui:

// В Avalonia.Controls.dll (спрощено)
[assembly: XmlnsDefinition(
    "https://github.com/avaloniaui",
    "Avalonia.Controls")]

[assembly: XmlnsDefinition(
    "https://github.com/avaloniaui",
    "Avalonia.Media")]

[assembly: XmlnsDefinition(
    "https://github.com/avaloniaui",
    "Avalonia.Layout")]

// ... інші namespace'и

Тому <Button>, <TextBlock>, <Grid>, <StackPanel> — все це доступне без prefix'а, так само як у WPF.


Відмінність 3: using: замість clr-namespace:

У WPF для підключення власних класів ви писали:

xmlns:local="clr-namespace:MyApp"
xmlns:vm="clr-namespace:MyApp.ViewModels"
xmlns:ctrl="clr-namespace:MyApp.Controls"

В Avalonia є скорочений еквівалентний синтаксис через using::

xmlns:local="using:MyApp"
xmlns:vm="using:MyApp.ViewModels"
xmlns:ctrl="using:MyApp.Controls"

Обидва синтаксиси підтримуються в Avalonia. Але using: є рекомендованим — він коротший і більш читабельний. Всі шаблони Avalonia генерують саме using:.

Для класів з іншої збірки формат такий самий, як і в WPF — потрібен assembly=:

<!-- WPF -->
xmlns:ext="clr-namespace:SomeLib.Extensions;assembly=SomeLib"

<!-- Avalonia (також працює clr-namespace, але using: скорочує запис) -->
xmlns:ext="using:SomeLib.Extensions;assembly=SomeLib"
Якщо ви портуєте WPF-проєкт на Avalonia — clr-namespace: не потребує заміни. Він по-справжньому сумісний. Замінюйте на using: лише за бажанням або при рефакторингу.

Відмінність 4: Ресурси та ThemeResource

Механізм ресурсів у Avalonia — той самий що й у WPF: ResourceDictionary, x:Key, StaticResource, DynamicResource, MergedDictionaries. Всі ці концепції переносяться без змін.

Але Avalonia додає один новий Markup Extension — ThemeResource.

Що таке ThemeResource

ThemeResource — це спеціалізований аналог DynamicResource, який розуміє Avalonia-теми (Light / Dark / HighContrast). Він шукає ресурс у активній темі застосунку, а не тільки у звичайних ResourceDictionary.

<!-- Ресурс реагує на перемикання теми Light/Dark -->
<TextBlock Foreground="{ThemeResource SystemControlForegroundBaseHighBrush}"/>

<!-- DynamicResource — шукає у ResourceDictionary ланцюжку -->
<TextBlock Foreground="{DynamicResource MyCustomBrush}"/>

<!-- StaticResource — одноразовий пошук при ініціалізації -->
<Button Background="{StaticResource PrimaryBrush}"/>

ThemeResource особливо важливий при використанні Avalonia Fluent Theme: всі системні кольори теми (фон вікна, колір тексту, колір акценту) задані через ThemeResource. Якщо ви хочете, щоб ваш кастомний контрол "підхоплював" активну тему автоматично — використовуйте ThemeResource для системних кольорів.

Ресурси: що залишається незмінним

<!-- Це 100% однаково у WPF і Avalonia -->
<Window.Resources>
    <SolidColorBrush x:Key="MyBrush" Color="#2563eb"/>
    <sys:String x:Key="MyString">Привіт</sys:String>
</Window.Resources>

<!-- StaticResource та DynamicResource — без змін -->
<Button Background="{StaticResource MyBrush}"/>
<TextBlock Foreground="{DynamicResource MyBrush}"/>
<!-- MergedDictionaries — без змін -->
<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="avares://MyApp/Resources/Colors.axaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

Одна деталь: у Avalonia для embedded-ресурсів (файлів, вбудованих у збірку) шлях починається з avares:// протоколу замість звичайного відносного шляху:

<!-- WPF: відносний шлях -->
<ResourceDictionary Source="Resources/Colors.xaml"/>

<!-- Avalonia: avares:// протокол для embedded-ресурсів -->
<ResourceDictionary Source="avares://MyAvaloniaApp/Resources/Colors.axaml"/>

Де MyAvaloniaApp — назва збірки (Assembly Name з .csproj). Це трохи детальніше, але дозволяє точно адресувати ресурси з різних збірок.

avares:// (Avalonia Resources) — URI-схема для доступу до файлів, вбудованих у GZ-архів всередині Avalonia-збірки. Аналог pack://application:,,,/ у WPF, але читабельніший. При розробці на Desktop можна використовувати і відносні шляхи — Avalonia їх теж розуміє, але avares:// є правильнішим для production.

Avalonia Previewer: живий перегляд без WPF Designer

У WPF є вбудований XAML Designer у Visual Studio — панель, що показує вікно поруч з кодом. Він часто "ламається", вимагає перезапуску і підтримує лише Windows.

Avalonia пішла іншим шляхом: Avalonia Previewer — окремий процес, що рендерить .axaml через власний движок Avalonia і показує результат у боковій панелі IDE.

Як запустити Previewer

  1. Відкрийте будь-який .axaml файл
  2. У верхньому правому куті редактора — кнопка "Open Avalonia XAML Preview" або вкладка "Preview"
  3. Preview завантажується автоматично і оновлюється при кожній зміні файлу

Чому Previewer кращий за WPF Designer

АспектWPF XAML DesignerAvalonia Previewer
ПлатформаWindows onlyWindows, macOS, Linux
РендерингWPF (DirectX)Реальний Avalonia (Skia)
ТочністьЧасто відрізняється від runtimeПіксельно точний
СтабільністьЧасто зависає, падаєСтабільніший
Hot Reload✅ Є✅ Є
DataContext у previewСкладно налаштуватиd:DataContext або DesignData

Avalonia Previewer рендерить той самий Skia-рушій, що й реальний застосунок — тому те, що ви бачите у preview, точно відповідає результату при запуску.


Повна порівняльна таблиця: WPF XAML vs Avalonia XAML

Ось side-by-side порівняння 15 ключових конструкцій:

КонструкціяWPF XAMLAvalonia XAML
Розширення файлу.xaml.axaml (рекомендовано)
Default namespacehttp://schemas.microsoft.com/winfx/2006/xaml/presentationhttps://github.com/avaloniaui
XAML directive namespacehttp://schemas.microsoft.com/winfx/2006/xaml✅ Той самий
Клас window: Window: Window
x:Classx:Class="MyApp.MainWindow"✅ Той самий
x:Namex:Name="myButton"✅ Той самий
Підключення CLR namespaceclr-namespace:MyAppusing:MyApp (або clr-namespace:)
StaticResource{StaticResource MyKey}✅ Той самий
DynamicResource{DynamicResource MyKey}✅ Той самий
Тематичний ресурс❌ Немає{ThemeResource SystemBrush}
Шлях до ресурсівResources/Colors.xamlavares://App/Resources/Colors.axaml
Attached PropertiesGrid.Row="1"✅ Той самий
Property Element Syntax<Button.Background>✅ Той самий
x:Key у ResourceDictionaryx:Key="MyKey"✅ Той самий
Design-time namespacexmlns:d="..."✅ Той самий

Практичні завдання