Якщо ви вивчали попередні статті блоку Layout — ви вже знаєте Grid, StackPanel, DockPanel, Canvas, Border та ScrollViewer. Все це, разом з Grid.Row, ColumnSpan, Margin, Padding та Alignment — присутнє в Avalonia один-в-один. XAML-код з WPF-статей перенесеться в Avalonia-проєкт з мінімальними змінами.
Але Avalonia — не просто "WPF для Linux". Це самостійна платформа що розвивається з 2013 року, та за цей час накопичила ряд принципових покращень у системі layout. Деякі з них настільки зручні, що WPF-розробники, побачивши їх вперше, запитують: "Чому цього немає у WPF?".
Ця стаття — практичний гід: що залишилось, що покращилось і що з'явилось нового.
Перш за все — важлива новина: все основне — ідентично. Якщо ви знаєте WPF layout — ви вже знаєте Avalonia layout на 80%.
| Елемент | WPF | Avalonia | Сумісність |
|---|---|---|---|
Grid | ✅ | ✅ | Повна (+ RowSpacing/ColumnSpacing) |
StackPanel | ✅ | ✅ | Повна (+ Spacing) |
DockPanel | ✅ | ✅ | Повна |
WrapPanel | ✅ | ✅ | Повна |
Canvas | ✅ | ✅ | Повна |
UniformGrid | ✅ | ✅ | Повна |
ScrollViewer | ✅ | ✅ | Майже повна |
Border | ✅ | ✅ | Повна (+ BoxShadow) |
ViewBox | ✅ | ✅ | Повна |
GridSplitter | ✅ | ✅ | Повна |
Panel.ZIndex | ✅ | ✅ | Повна |
Margin, Padding | ✅ | ✅ | Повна |
HorizontalAlignment | ✅ | ✅ | Повна |
MinWidth/MaxWidth | ✅ | ✅ | Повна |
Практично весь XAML з попередніх статей запуститься в Avalonia-проєкті після лише одного кроку: замінити xmlns на Avalonia-неймспейси.
<!-- WPF -->
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Avalonia — лише це відрізняється: -->
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
Решта XAML всередині — може залишатись без змін. Grid, RowDefinitions, Grid.Row, ColumnSpan, Margin, Border з CornerRadius — все це Avalonia розуміє і рендерить так само.
Spacing: вбудований відступ між елементамиЦе перше і найочевидніше покращення, яке одразу помічають WPF-розробники.
У WPF немає вбудованого відступу між дочірніми елементами StackPanel. Щоб додати відступи між кнопками або секціями — треба було або додавати Margin на кожен елемент, або створювати глобальний стиль:
<!-- WPF: Margin на кожному елементі (Margin-хак) -->
<StackPanel>
<Button Margin="0,0,0,8" Content="Кнопка 1"/>
<Button Margin="0,0,0,8" Content="Кнопка 2"/>
<Button Margin="0,0,0,8" Content="Кнопка 3"/>
<!-- Остання кнопка — зайвий нижній відступ! -->
</StackPanel>
Проблема Margin-хаку: останній елемент отримує зайвий відступ (у прикладі — 8px знизу після "Кнопка 3"). Щоб виправити — треба або не давати Margin останньому, або давати від'ємний Margin контейнеру. Це незручно.
Деякі WPF-розробники пишуть стиль:
<!-- WPF: Style-підхід (краще але все одно обхід) -->
<StackPanel>
<StackPanel.Resources>
<Style TargetType="Button">
<Setter Property="Margin" Value="0,0,0,8"/>
</Style>
</StackPanel.Resources>
<Button Content="Кнопка 1"/>
<Button Content="Кнопка 2"/>
<!-- Але стиль застосовується до ВСІХ кнопок включно з останньою -->
</StackPanel>
SpacingAvalonia додала властивість Spacing прямо до StackPanel. Вона додає рівномірний відступ між елементами — але не після останнього:
<!-- Avalonia: просто і елегантно -->
<StackPanel Spacing="8">
<Button Content="Кнопка 1"/>
<Button Content="Кнопка 2"/>
<Button Content="Кнопка 3"/>
<!-- Відступ тільки між елементами: 8px між 1-2 і між 2-3 -->
<!-- Після "Кнопка 3" — ЖОДНОГО зайвого відступу -->
</StackPanel>
Результат ідеальний: Spacing додає (n-1) відступів для n елементів. Це точно те, що потрібно у 99% випадків.
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<StackPanel Margin="20" Spacing="32">
<!-- Вертикальний StackPanel зі Spacing -->
<TextBlock Text="Вертикальний StackPanel Spacing=12"
FontWeight="SemiBold" Foreground="#1e293b" FontSize="14"/>
<StackPanel Spacing="12">
<Border Background="#eff6ff" CornerRadius="8" Padding="14,10">
<TextBlock Text="📧 Вхідні (12px нижче)" Foreground="#1e40af"/>
</Border>
<Border Background="#f0fdf4" CornerRadius="8" Padding="14,10">
<TextBlock Text="📤 Надіслані (12px нижче)" Foreground="#166534"/>
</Border>
<Border Background="#fdf4ff" CornerRadius="8" Padding="14,10">
<TextBlock Text="⭐ Важливі (12px нижче)" Foreground="#6b21a8"/>
</Border>
<Border Background="#fff7ed" CornerRadius="8" Padding="14,10">
<TextBlock Text="🗑️ Кошик (без відступу знизу!)" Foreground="#9a3412"/>
</Border>
</StackPanel>
<!-- Горизонтальний StackPanel зі Spacing -->
<TextBlock Text="Горизонтальний StackPanel Spacing=8"
FontWeight="SemiBold" Foreground="#1e293b" FontSize="14"/>
<StackPanel Orientation="Horizontal" Spacing="8">
<Button Content="Зберегти" Background="#2563eb" Foreground="White"
Padding="16,8" CornerRadius="6"/>
<Button Content="Скасувати" Padding="16,8" CornerRadius="6"
Background="#f1f5f9" Foreground="#374151"/>
<Button Content="Видалити" Padding="16,8" CornerRadius="6"
Background="#fee2e2" Foreground="#dc2626"/>
</StackPanel>
<TextBlock Text="Без Spacing = без зайвого Margin після останнього елемента ✅"
FontSize="12" Foreground="#64748b" FontStyle="Italic"/>
</StackPanel>
Spacing у Grid: RowSpacing та ColumnSpacingAvalonia також додала відступи між рядками і колонками у Grid:
<!-- WPF: щоб зробити відступ між рядками — потрібен "порожній" рядок висотою 8px -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="8"/> <!-- Штучний відступ! -->
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Рядок 1"/>
<!-- Grid.Row="1" — це порожній рядок-відступ -->
<TextBlock Grid.Row="2" Text="Рядок 2"/>
</Grid>
<!-- Avalonia: просто RowSpacing -->
<Grid RowSpacing="8" ColumnSpacing="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Ім'я:"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="Іван"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Email:"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="ivan@example.com"/>
<!-- RowSpacing=8 автоматично між рядками, ColumnSpacing=12 між колонками -->
</Grid>
Результат: жодних фіктивних рядків-роздільників. Grid-структура лишається чистою, а відступи декларуються де належить — на самому Grid.
RelativePanel: позиціонування відносно сусідівGrid чудовий для структурованих сіток — але іноді вам потрібно позиціонувати елементи відносно один одного: "цей — справа від того", "цей — під тим", "цей — вирівняний по правому краю панелі". Саме це вирішує RelativePanel.
Уявіть інтерфейс пошуку: поле TextBox по ширині займає весь рядок, але з правого боку — кнопка "×" (очистити). При зміні розміру вікна TextBox розтягується, а кнопка має залишатись у правому куті TextBox. У Grid для цього потрібна складна структура. У RelativePanel — одна рядок.
RelativePanel — це панель де кожен дочірній елемент може декларувати своє розташування через Attached Properties відносно:
RelativePanelRelativePanel має дві категорії Attached Properties:
Відносно панелі:
| Attached Property | Значення |
|---|---|
RelativePanel.AlignLeftWithPanel | Вирівняти лівий край з панеллю |
RelativePanel.AlignRightWithPanel | Вирівняти правий край з панеллю |
RelativePanel.AlignTopWithPanel | Вирівняти верхній край з панеллю |
RelativePanel.AlignBottomWithPanel | Вирівняти нижній край з панеллю |
RelativePanel.AlignHorizontalCenterWithPanel | Горизонтальний центр |
RelativePanel.AlignVerticalCenterWithPanel | Вертикальний центр |
Відносно іншого елемента ({x:Reference} або x:Name):
| Attached Property | Значення |
|---|---|
RelativePanel.RightOf | Розмістити правіше вказаного елемента |
RelativePanel.LeftOf | Розмістити лівіше вказаного елемента |
RelativePanel.Below | Розмістити нижче вказаного елемента |
RelativePanel.Above | Розмістити вище вказаного елемента |
RelativePanel.AlignLeftWith | Вирівняти лівий край з вказаним елементом |
RelativePanel.AlignRightWith | Вирівняти правий край з вказаним елементом |
RelativePanel.AlignTopWith | Вирівняти верхній край з вказаним елементом |
RelativePanel.AlignBottomWith | Вирівняти нижній край з вказаним елементом |
Кожен елемент у RelativePanel декларує своє розміщення через ці Attached Properties:
<RelativePanel>
<!-- Заголовок: вгорі зліва від панелі -->
<TextBlock x:Name="title"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignTopWithPanel="True"
Text="Налаштування профілю"
FontSize="18" FontWeight="Bold"
Margin="0,0,0,16"/>
<!-- Аватар: нижче заголовка, вирівняний зліва -->
<Border x:Name="avatar"
RelativePanel.Below="title"
RelativePanel.AlignLeftWithPanel="True"
Width="64" Height="64"
Background="#2563eb" CornerRadius="32"
Margin="0,0,0,12">
<TextBlock Text="🧑" FontSize="28" HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<!-- Ім'я: справа від аватара, вирівняний по його верху -->
<TextBlock x:Name="userName"
RelativePanel.RightOf="avatar"
RelativePanel.AlignTopWith="avatar"
Text="Іван Петренко"
FontSize="16" FontWeight="SemiBold"
Margin="12,0,0,0"/>
<!-- Посада: нижче імені, вирівняна зліва з ім'ям -->
<TextBlock RelativePanel.Below="userName"
RelativePanel.AlignLeftWith="userName"
Text="Junior WPF Developer"
Foreground="#64748b"
Margin="12,4,0,0"/>
<!-- Кнопка "Редагувати": у правому нижньому куті панелі -->
<Button RelativePanel.AlignRightWithPanel="True"
RelativePanel.AlignBottomWithPanel="True"
Content="✏️ Редагувати"
Padding="12,6" Background="#2563eb" Foreground="White"
CornerRadius="6"/>
</RelativePanel>
Зверніть увагу: у RelativePanel немає Grid.Row або будь-якого іншого індексного позиціонування. Кожен елемент сам декларує своє місце і відносний елемент-сусід.
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<Border Width="340" Background="White" BorderBrush="#e2e8f0"
BorderThickness="1" CornerRadius="12" Padding="20"
HorizontalAlignment="Center" VerticalAlignment="Center"
Margin="20">
<RelativePanel>
<!-- Заголовок секції -->
<TextBlock x:Name="sectionTitle"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignTopWithPanel="True"
Text="Профіль користувача"
FontSize="15" FontWeight="Bold"
Foreground="#0f172a"
Margin="0,0,0,16"/>
<!-- Аватар -->
<Border x:Name="avatarBorder"
RelativePanel.Below="sectionTitle"
RelativePanel.AlignLeftWithPanel="True"
Width="56" Height="56" CornerRadius="28"
Background="#2563eb">
<TextBlock Text="ІП" Foreground="White" FontSize="18"
FontWeight="Bold"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<!-- Ім'я: справа від аватара -->
<TextBlock x:Name="fullName"
RelativePanel.RightOf="avatarBorder"
RelativePanel.AlignTopWith="avatarBorder"
Text="Іван Петренко"
FontSize="15" FontWeight="SemiBold"
Foreground="#1e293b"
Margin="14,4,0,0"/>
<!-- Посада -->
<TextBlock x:Name="jobTitle"
RelativePanel.Below="fullName"
RelativePanel.AlignLeftWith="fullName"
Text="Frontend WPF Developer"
FontSize="12" Foreground="#64748b"
Margin="14,2,0,0"/>
<!-- Badge "Online" -->
<Border RelativePanel.Below="jobTitle"
RelativePanel.AlignLeftWith="jobTitle"
Background="#dcfce7" CornerRadius="10"
Padding="8,3" Margin="14,6,0,0">
<TextBlock Text="● Онлайн" Foreground="#16a34a" FontSize="11"
FontWeight="SemiBold"/>
</Border>
<!-- Email під аватаром: нижче avatarBorder, вирівняний зліва з панеллю -->
<Border x:Name="emailRow"
RelativePanel.Below="avatarBorder"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignRightWithPanel="True"
BorderBrush="#f1f5f9" BorderThickness="0,1,0,0"
Margin="0,16,0,0" Padding="0,12,0,0">
<StackPanel Spacing="6">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="📧" FontSize="13"/>
<TextBlock Text="ivan.petrenko@company.ua" FontSize="13"
Foreground="#374151"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="📍" FontSize="13"/>
<TextBlock Text="Київ, Україна" FontSize="13"
Foreground="#374151"/>
</StackPanel>
</StackPanel>
</Border>
<!-- Кнопки: нижче emailRow -->
<StackPanel Orientation="Horizontal" Spacing="8"
RelativePanel.Below="emailRow"
RelativePanel.AlignRightWithPanel="True"
Margin="0,12,0,0">
<Button Content="Повідомлення" Padding="10,6" FontSize="12"
Background="#f1f5f9" Foreground="#374151" CornerRadius="6"/>
<Button Content="✏️ Редагувати" Padding="10,6" FontSize="12"
Background="#2563eb" Foreground="White" CornerRadius="6"/>
</StackPanel>
</RelativePanel>
</Border>
RelativePanel — не заміна Grid. Це зброя для специфічних сценаріїв:
✅ Overlapping елементів: Бейдж "●" поверх аватара. У Grid — складно, у RelativePanel — AlignRightWith + AlignTopWith.
✅ Float UI: Кнопка в кутку картки незалежно від контенту — AlignRightWithPanel + AlignBottomWithPanel.
✅ Адаптивне перекомпонування: Елементи перебудовуються між режимами без перепису структури Grid.
✅ Liquid layout: Коли елементи "прив'язані" один до одного й мають рухатись разом.
❌ Grid краший для: рівномірних форм (Label + Input), складних таблиць, фіксованих областей з чіткою структурою рядків/колонок.
SplitView: адаптивний каркас застосункуSplitView — це контрол, призначений спеціально для реалізації патерну "Hamburger Menu" (бічна панель з навігацією). У WPF для створення такого меню доводилося писати складний код управління анімацією, розширенням колонок Grid або використовувати сторонні бібліотеки (наприклад, MahApps.Metro).
В Avalonia SplitView доступний "з коробки". Він ділить доступний простір на дві частини:
SplitViewIsPaneOpen: (bool) Відкрита чи закрита бічна панель. Можна легко прив'язати до ToggleButton (гамбургер-кнопки).DisplayMode: Визначає як панель взаємодіє з основним контентом.
Inline: Відкриваючись, штовхає контент (змінює його ширину).CompactInline: Закрита панель показує вузьку смужку (наприклад, лише іконки), відкрита — штовхає контент.Overlay: Панель виїжджає поверх контенту (ідеально для мобільних екранів).CompactOverlay: Закрита — іконки, відкрита — виїжджає поверх контенту.OpenPaneLength: Ширина відкритої панелі.CompactPaneLength: Ширина закритої панелі у компактному режимі.PanePlacement: Розташування панелі (Left або Right).<SplitView IsPaneOpen="{Binding IsSidebarOpen}"
DisplayMode="CompactInline"
CompactPaneLength="48"
OpenPaneLength="200">
<SplitView.Pane>
<!-- Вміст бічної панелі: іконки + текст -->
<StackPanel Spacing="8" Margin="0,8">
<Button Command="{Binding ToggleSidebarCommand}"
Content="☰" Width="48" Background="Transparent"/>
<ListBox>
<ListBoxItem>
<StackPanel Orientation="Horizontal" Spacing="12">
<TextBlock Text="🏠" Width="24" TextAlignment="Center"/>
<TextBlock Text="Головна"/>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel Orientation="Horizontal" Spacing="12">
<TextBlock Text="⚙️" Width="24" TextAlignment="Center"/>
<TextBlock Text="Налаштування"/>
</StackPanel>
</ListBoxItem>
</ListBox>
</StackPanel>
</SplitView.Pane>
<SplitView.Content>
<!-- Основний контент екрану -->
<Border Background="#f1f5f9" Padding="24">
<TextBlock Text="Тут відображається обрана сторінка"
FontSize="20" Foreground="#333"/>
</Border>
</SplitView.Content>
</SplitView>
Цей елемент є основою для застосунків, які мають добре працювати як на широких десктопних моніторах (режим Inline), так і на телефонах/планшетах (режим Overlay).
Expander: розгортальні секціїТак, Expander є і у WPF, проте в Avalonia він має деякі переваги у побудові адаптивних layout'ів завдяки вбудованій системі інтеграції з анімаціями (Transitions).
Avalonia дозволяє легко задавати ContentTransition для згладженого переходу між розгорнутим і згорнутим станом, чого завжди не вистачало WPF, де вміст з'являвся стрибком і вимагав складних Storyboard.
<Expander Header="Розгорніть для деталей">
<!-- Додаємо анімацію вмісту -->
<Expander.ContentTransition>
<CrossFade Duration="0:0:0.2" />
</Expander.ContentTransition>
<StackPanel Spacing="8" Margin="8">
<TextBlock Text="Цей текст з'являється плавно з ефектом fade." />
<TextBlock Text="А висота панелі розширюється за допомогою вбудованої анімації Expander." />
</StackPanel>
</Expander>
ItemsRepeater: легковісний layout для колекційУ WPF основним контролом для списків є ListBox або ItemsControl. Але коли вам потрібно просто вивести 1000 карток з даними у вигляді сітки — ListBox додає багато зайвого навантаження (виділення, фокус, складні візуальні дерева).
В Avalonia з'явився ItemsRepeater — контрол для високопродуктивного рендерингу прив'язаних даних. Він фокусується лише на Layout та Virtualization (не керує виділенням елементів на відміну від ListBox).
ItemsRepeater підтримує різні алгоритми розташування через властивість Layout:
StackLayout — класичний стек (як StackPanel).WrapLayout — перенесення на новий рядок (як WrapPanel).UniformGridLayout — сітка з однаковими комірками, яка автоматично змінює кількість колонок залежно від ширини екрану (ідеально для карток товарів/відео).LinedFlowLayout — розташування як в галереї фотографій (вирівнювання за рядками).Це класичний патерн "Responsive Grid", який переносить картки і заповнює доступний простір екрану:
<ScrollViewer>
<ItemsRepeater ItemsSource="{Binding Products}">
<ItemsRepeater.Layout>
<!-- Адаптивна сітка з відступами 16px і мінімальним розміром комірки 280x320 -->
<UniformGridLayout MinItemWidth="280" MinItemHeight="320"
MinColumnSpacing="16" MinRowSpacing="16" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<Border Background="White" CornerRadius="8" Padding="16" BoxShadow="0 4 12 0 #1A000000">
<StackPanel Spacing="8">
<Image Source="{Binding ContextImage}" Height="150" Stretch="UniformToFill"/>
<TextBlock Text="{Binding Title}" FontWeight="Bold" FontSize="16" />
<TextBlock Text="{Binding Price}" Foreground="#2563eb" FontWeight="SemiBold"/>
</StackPanel>
</Border>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ScrollViewer>
Transitions)Хоча анімація — це окрема тема, в Avalonia вона настільки глибоко інтегрована безпосередньо в систему Layout, що заслуговує згадки тут.
Там, де WPF вимагає ColorAnimation в EventTrigger або написання коду C#, Avalonia пропонує CSS-подібні Transitions. Будь-яка Panel може плавно анімувати зміну власних властивостей (Width, Height, Margin, Background).
<Border Background="#2563eb" Width="100" Height="40" CornerRadius="4">
<!-- Реєстрація того, ЯК панель повинна реагувати на зміну її Width -->
<Border.Transitions>
<Transitions>
<DoubleTransition Property="Width" Duration="0:0:0.3"/>
<BrushTransition Property="Background" Duration="0:0:0.3"/>
</Transitions>
</Border.Transitions>
<!-- Якщо змінити клас (через C# або CSS-стилі в Avalonia),
Width зміниться з 100 до 200 ПЛАВНО за 0.3 секунди -->
</Border>
Це дозволяє створювати інтерфейси, які здаються дуже "живими" (fluid interfaces). Змінили властивість розміру або позиції в C# — і елемент не "стрибне" миттєво, а плавно зсуне сусідні елементи у layout.
Avalonia бере все найкраще з WPF Layout (Grid, Margin/Padding патерни, двопрохідну модель вимірювань Measure/Arrange) і додає до неї те, що розробники просили роками:
Ці інструменти роблять створення високоякісного UI в Avalonia швидшим і виразнішим, залишаючись при цьому у знайомій усім WPF-розробникам XAML-парадигмі.
Адаптивний Layout та найкращі практики
Responsive Layout у WPF: Star sizing, Visibility (Collapsed/Hidden), SizeChanged event, MinWidth як responsive tool, WrapPanel для адаптивних сіток, best practices.
Button, Image, ProgressBar та інші базові контроли
Вивчаємо найбільш вживані контроли WPF — кнопки, зображення, індикатори прогресу, слайдери та спливаючі підказки. Кожен контрол розбирається від базових властивостей до нетривіальних деталей.