WPF-застосунок — на відміну від мобільних додатків — запускається у вікні довільного розміру. Користувач може розтягнути вікно на весь екран, стиснути до мінімуму або розмістити поряд з іншими вікнами. Якщо інтерфейс не адаптується до цих змін — він виглядає зламаним.
Хороша новина: WPF-система layout вже побудована з урахуванням адаптивності. *-розміри у Grid, WrapPanel, ScrollViewer — все це автоматично реагує на зміну доступного місця. Ваша задача — правильно скласти ці блоки.
*) — пропорційний розподіл залишкового простору у Grid. Visibility — перерахунок простору при Collapsed (звільняє місце) vs Hidden (зберігає місце). SizeChanged — подія що виникає при зміні розміру елемента. ActualWidth/ActualHeight — реальний розмір елемента після Layout Pass. UseLayoutRounding — вирівнювання до цілих пікселів для чіткості.Ви вже знайомі з * з попередньої статті. Тепер розглянемо його як головний інструмент responsive layout.
* кращий за фіксовані пікселі<!-- ПОГАНО: фіксований розмір — при зменшенні вікна кнопки ховаються -->
<Grid Width="800">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="600"/> <!-- Завжди 600px -->
<ColumnDefinition Width="200"/> <!-- Завжди 200px -->
</Grid.ColumnDefinitions>
...
</Grid>
<!-- ДОБРЕ: пропорційний розподіл — адаптується до будь-якої ширини -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"/> <!-- 75% простору -->
<ColumnDefinition Width="*"/> <!-- 25% простору -->
</Grid.ColumnDefinitions>
...
</Grid>
При ширині вікна 800px: 3* → 600px, * → 200px — той самий результат.
При ширині 1200px: 3* → 900px, * → 300px — масштабується пропорційно.
При ширині 400px: 3* → 300px, * → 100px — залишається пропорційним.
Найефективніша комбінація для адаптивних форм:
<Grid>
<Grid.ColumnDefinitions>
<!-- Auto для фіксованих елементів (мітки, іконки) -->
<ColumnDefinition Width="Auto"/>
<!-- * для елементів що мають розтягуватись (поля вводу, списки) -->
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Мітка auto = її природній розмір -->
<TextBlock Grid.Column="0" Text="Email:" Margin="0,0,8,0"
VerticalAlignment="Center"/>
<!-- Поле вводу * = займає весь залишок -->
<TextBox Grid.Column="1" Text="user@example.com"/>
</Grid>
Цей патерн працює при будь-якій ширині вікна: мітка займає рівно стільки скільки їй треба, поле вводу займає решту.
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<StackPanel Margin="16" Spacing="20">
<TextBlock Text="❌ Фіксована ширина (погано)" FontWeight="Bold" Foreground="#dc2626"/>
<Border Background="White" BorderBrush="#fecaca" BorderThickness="1" CornerRadius="8" Padding="16">
<StackPanel Spacing="8">
<!-- Поля мають фіксовану ширину — при вузькому вікні виступають -->
<TextBlock Text="Ім'я:" FontWeight="SemiBold"/>
<TextBox Width="300" HorizontalAlignment="Left" Padding="8,6" Text="Іван Петренко"/>
<TextBlock Text="Email:" FontWeight="SemiBold"/>
<TextBox Width="300" HorizontalAlignment="Left" Padding="8,6" Text="ivan@example.com"/>
</StackPanel>
</Border>
<TextBlock Text="✅ Star sizing Auto + * (добре)" FontWeight="Bold" Foreground="#16a34a"/>
<Border Background="White" BorderBrush="#bbf7d0" BorderThickness="1" CornerRadius="8" Padding="16">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="8"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="8"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Ім'я:" FontWeight="SemiBold"
VerticalAlignment="Center" Margin="0,0,12,0"/>
<TextBox Grid.Row="0" Grid.Column="1" Padding="8,6" Text="Іван Петренко"
BorderBrush="#d1d5db"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Email:" FontWeight="SemiBold"
VerticalAlignment="Center" Margin="0,0,12,0"/>
<TextBox Grid.Row="2" Grid.Column="1" Padding="8,6" Text="ivan@example.com"
BorderBrush="#d1d5db"/>
<StackPanel Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2"
Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8">
<Button Content="Скасувати" Padding="16,7" MinWidth="100"/>
<Button Content="Зберегти" Padding="16,7" MinWidth="100"
Background="#2563eb" Foreground="White"/>
</StackPanel>
</Grid>
</Border>
</StackPanel>
Visibility — властивість що є у кожного UIElement. Вона контролює видимість і участь у layout:
<!-- Visible: елемент видимий і займає місце (за замовчуванням) -->
<Button Visibility="Visible" Content="Видима кнопка"/>
<!-- Collapsed: елемент невидимий і НЕ займає місце (layout перебудовується!) -->
<Button Visibility="Collapsed" Content="Прихована кнопка (0×0 у layout)"/>
<!-- Hidden: елемент невидимий, але ВСЕ ЩЕ займає місце -->
<Button Visibility="Hidden" Content="Невидима, але місце є"/>
[Button 1] [Button 2] [Button 3]
Якщо Button 2 = Collapsed:
[Button 1] [Button 3] ← Layout перебудувався
Якщо Button 2 = Hidden:
[Button 1] [ ] [Button 3] ← Місце збереглось
Collapsed — для responsive приховування (sidebar, panel, toolbar).Hidden — для синхронізованих layout де ви хочете резервувати місце (наприклад, помилки валідації що не повинні зміщувати форму).
<!-- Типовий патерн: помилка валідації що не зміщує форму -->
<TextBlock Text="Поле обов'язкове!" Foreground="Red"
Visibility="Hidden" <!-- зберігає місце під повідомлення -->
x:Name="errorMessage"/>
<!-- На відміну від Collapsed, TextBox не стрибає вгору/вниз при показі помилки -->
// Перемкнути sidebar
private void ToggleSidebar(bool show)
{
sidebar.Visibility = show ? Visibility.Visible : Visibility.Collapsed;
}
// Реакція на зміну розміру
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.NewSize.Width < 800)
{
sidebar.Visibility = Visibility.Collapsed;
sidebarSplitter.Visibility = Visibility.Collapsed;
}
else
{
sidebar.Visibility = Visibility.Visible;
sidebarSplitter.Visibility = Visibility.Visible;
}
}
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<StackPanel Margin="16" Spacing="20">
<TextBlock Text="StackPanel з Collapsed елементом:"
FontWeight="SemiBold" Foreground="#374151"/>
<Border BorderBrush="#e5e7eb" BorderThickness="1" Padding="12" CornerRadius="6" Background="#f9fafb">
<StackPanel Orientation="Horizontal" Spacing="8">
<Border Background="#2563eb" CornerRadius="4" Padding="12,6">
<TextBlock Text="Кнопка 1" Foreground="White"/>
</Border>
<!-- Collapsed: займає 0 простору, сусіди зсуваються -->
<Border Background="#dc2626" CornerRadius="4" Padding="12,6"
Visibility="Collapsed">
<TextBlock Text="COLLAPSED" Foreground="White"/>
</Border>
<Border Background="#059669" CornerRadius="4" Padding="12,6">
<TextBlock Text="Кнопка 3" Foreground="White"/>
</Border>
</StackPanel>
</Border>
<TextBlock Text="StackPanel з Hidden елементом:"
FontWeight="SemiBold" Foreground="#374151"/>
<Border BorderBrush="#e5e7eb" BorderThickness="1" Padding="12" CornerRadius="6" Background="#f9fafb">
<StackPanel Orientation="Horizontal" Spacing="8">
<Border Background="#2563eb" CornerRadius="4" Padding="12,6">
<TextBlock Text="Кнопка 1" Foreground="White"/>
</Border>
<!-- Hidden: займає місце, але прозора -->
<Border Background="#7c3aed" CornerRadius="4" Padding="12,6"
Visibility="Hidden">
<TextBlock Text="HIDDEN" Foreground="White"/>
</Border>
<Border Background="#059669" CornerRadius="4" Padding="12,6">
<TextBlock Text="Кнопка 3" Foreground="White"/>
</Border>
</StackPanel>
</Border>
<TextBlock Text="Видно: між 'Кнопка 1' і 'Кнопка 3' є порожнє місце (Hidden),
але немає порожнього місця (Collapsed)"
Foreground="#64748b" FontSize="12" TextWrapping="Wrap"/>
</StackPanel>
SizeChanged — подія що спрацьовує при будь-якій зміні ActualWidth або ActualHeight елемента. Її можна підписати на Window, Grid, Border чи будь-який інший FrameworkElement.
<!-- XAML: підписуємо Window або Grid -->
<Window SizeChanged="Window_SizeChanged">
<!-- або -->
<Grid SizeChanged="ContentGrid_SizeChanged">
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
// e.NewSize: новий розмір
// e.PreviousSize: попередній розмір
// e.WidthChanged: чи змінилась ширина
// e.HeightChanged: чи змінилась висота
double newWidth = e.NewSize.Width;
// Adaptive breakpoints:
if (newWidth < 600)
{
// Compact mode
sidebar.Visibility = Visibility.Collapsed;
splitter.Visibility = Visibility.Collapsed;
compactToolbar.Visibility = Visibility.Visible;
fullToolbar.Visibility = Visibility.Collapsed;
}
else if (newWidth < 1000)
{
// Medium mode
sidebar.Visibility = Visibility.Collapsed;
splitter.Visibility = Visibility.Collapsed;
compactToolbar.Visibility = Visibility.Collapsed;
fullToolbar.Visibility = Visibility.Visible;
}
else
{
// Full mode
sidebar.Visibility = Visibility.Visible;
splitter.Visibility = Visibility.Visible;
compactToolbar.Visibility = Visibility.Collapsed;
fullToolbar.Visibility = Visibility.Visible;
}
}
ActualWidth та ActualHeightWidth і Height — це бажані розміри, що ви задаєте у XAML. ActualWidth і ActualHeight — реальні розміри після Layout Pass. Вони можуть відрізнятися:
// Після завантаження:
Console.WriteLine(myButton.Width); // NaN (не задано явно)
Console.WriteLine(myButton.ActualWidth); // 142.5 (реальний розмір після layout)
// ActualWidth доступний тільки після Loaded події:
myButton.Loaded += (s, e) =>
{
Console.WriteLine(myButton.ActualWidth); // Коректне значення
};
ActualWidth/ActualHeightнедоступні до того, як елемент пройшов перший Layout Pass. Якщо ви читаєте їх у конструкторі — отримаєте 0. Завжди підписуйтесь на Loaded або SizeChanged.<!-- MainWindow.xaml -->
<Window SizeChanged="Window_SizeChanged">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="sidebarColumn" Width="220" MinWidth="180" MaxWidth="320"/>
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Sidebar -->
<Border x:Name="sidebar" Grid.Column="0" Background="#1e293b">
<!-- Навігація -->
</Border>
<!-- GridSplitter -->
<GridSplitter x:Name="splitter" Grid.Column="1" Width="4"
HorizontalAlignment="Stretch"/>
<!-- Content -->
<Grid Grid.Column="2">
<!-- Compact toolbar (тільки при малій ширині) -->
<Button x:Name="menuButton" Content="☰"
HorizontalAlignment="Left" Margin="8"
Visibility="Collapsed"
Click="MenuButton_Click"/>
<!-- Основний вміст -->
<ScrollViewer Margin="0,40,0,0" x:Name="mainContent">
<!-- Dashboard контент -->
</ScrollViewer>
</Grid>
</Grid>
</Window>
// MainWindow.xaml.cs
private bool _sidebarVisible = true;
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
bool shouldShowSidebar = e.NewSize.Width >= 800;
if (shouldShowSidebar != _sidebarVisible)
{
_sidebarVisible = shouldShowSidebar;
UpdateSidebarVisibility(shouldShowSidebar);
}
}
private void UpdateSidebarVisibility(bool show)
{
sidebar.Visibility = show ? Visibility.Visible : Visibility.Collapsed;
splitter.Visibility = show ? Visibility.Visible : Visibility.Collapsed;
menuButton.Visibility = show ? Visibility.Collapsed : Visibility.Visible;
if (show)
sidebarColumn.Width = new GridLength(220);
else
sidebarColumn.Width = new GridLength(0);
}
private void MenuButton_Click(object sender, RoutedEventArgs e)
{
// Показати sidebar як overlay або toggle
UpdateSidebarVisibility(!_sidebarVisible);
}
MinWidth — це не просто обмеження розміру. Це responsive threshold: коли доступний простір стає менший за MinWidth, елемент зберігає свій мінімальний розмір і батьківський ScrollViewer починає прокручуватись:
<!-- ScrollViewer + Grid з мінімальним розміром: -->
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<Grid MinWidth="480"> <!-- Grid не буде вужчим за 480px -->
<!-- Контент форми -->
</Grid>
</ScrollViewer>
При ширині вікна > 480px — горизонтальний scroll не з'явиться.
При ширині вікна < 480px — з'явиться горизонтальний ScrollBar, Grid залишиться > 480px.
Це дозволяє мати мінімальний підтримуваний розмір без того, щоб елементи "ламались" при маленькому вікні.
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<Grid Height="260">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Margin="16,12" FontWeight="SemiBold" Foreground="#1e293b"
Text="Dashboard (мін. width=420 — пробуйте змінювати вікно)"/>
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Disabled">
<Grid MinWidth="420" Margin="16,0,16,16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="#1e40af" CornerRadius="8" Margin="0,0,8,0" Padding="16">
<StackPanel>
<TextBlock Text="📊 Продажі" Foreground="#93c5fd" FontSize="12"/>
<TextBlock Text="₴ 127,430" Foreground="White" FontSize="20" FontWeight="Bold"/>
<TextBlock Text="+8.2%" Foreground="#86efac" FontSize="12"/>
</StackPanel>
</Border>
<Border Grid.Column="1" Background="#059669" CornerRadius="8" Margin="4,0" Padding="16">
<StackPanel>
<TextBlock Text="👥 Клієнти" Foreground="#a7f3d0" FontSize="12"/>
<TextBlock Text="2,841" Foreground="White" FontSize="20" FontWeight="Bold"/>
<TextBlock Text="+15.1%" Foreground="#fde68a" FontSize="12"/>
</StackPanel>
</Border>
<Border Grid.Column="2" Background="#7c3aed" CornerRadius="8" Margin="8,0,0,0" Padding="16">
<StackPanel>
<TextBlock Text="📦 Замовлення" Foreground="#ddd6fe" FontSize="12"/>
<TextBlock Text="934" Foreground="White" FontSize="20" FontWeight="Bold"/>
<TextBlock Text="+3.7%" Foreground="#86efac" FontSize="12"/>
</StackPanel>
</Border>
</Grid>
</ScrollViewer>
</Grid>
WrapPanel — найближчий аналог CSS flex-wrap. Елементи вишиковуються у рядок і автоматично переносяться на наступний при нестачі місця. Без жодного C# коду.
<ScrollViewer>
<WrapPanel Margin="8" ItemWidth="200" ItemHeight="120">
<!-- При ширині 800px: 4 карток у рядку -->
<!-- При ширині 600px: 3 картки у рядку -->
<!-- При ширині 420px: 2 картки у рядку -->
<Border Background="White" BorderBrush="#e2e8f0" BorderThickness="1"
CornerRadius="8" Margin="6"/>
<!-- Ще картки... -->
</WrapPanel>
</ScrollViewer>
Переваги такого підходу:
ItemWidth/ItemHeightLoading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<ScrollViewer>
<WrapPanel Margin="12" ItemWidth="180" ItemHeight="130">
<Border Margin="4" Background="#eff6ff" CornerRadius="8" Padding="14">
<StackPanel Spacing="6">
<TextBlock Text="⚡" FontSize="22"/>
<TextBlock Text="Avalonia" FontWeight="Bold" Foreground="#1e40af" FontSize="13"/>
<TextBlock Text="Cross-platform .NET UI" FontSize="11" Foreground="#3b82f6" TextWrapping="Wrap"/>
</StackPanel>
</Border>
<Border Margin="4" Background="#f0fdf4" CornerRadius="8" Padding="14">
<StackPanel Spacing="6">
<TextBlock Text="🪟" FontSize="22"/>
<TextBlock Text="WPF" FontWeight="Bold" Foreground="#166534" FontSize="13"/>
<TextBlock Text="Windows Presentation Foundation" FontSize="11" Foreground="#16a34a" TextWrapping="Wrap"/>
</StackPanel>
</Border>
<Border Margin="4" Background="#fdf4ff" CornerRadius="8" Padding="14">
<StackPanel Spacing="6">
<TextBlock Text="📱" FontSize="22"/>
<TextBlock Text="MAUI" FontWeight="Bold" Foreground="#6b21a8" FontSize="13"/>
<TextBlock Text=".NET Multi-platform App UI" FontSize="11" Foreground="#8b5cf6" TextWrapping="Wrap"/>
</StackPanel>
</Border>
<Border Margin="4" Background="#fff7ed" CornerRadius="8" Padding="14">
<StackPanel Spacing="6">
<TextBlock Text="🌐" FontSize="22"/>
<TextBlock Text="Blazor" FontWeight="Bold" Foreground="#9a3412" FontSize="13"/>
<TextBlock Text="Web UI with C# and Razor" FontSize="11" Foreground="#ea580c" TextWrapping="Wrap"/>
</StackPanel>
</Border>
<Border Margin="4" Background="#fef2f2" CornerRadius="8" Padding="14">
<StackPanel Spacing="6">
<TextBlock Text="🖥️" FontSize="22"/>
<TextBlock Text="WinUI 3" FontWeight="Bold" Foreground="#991b1b" FontSize="13"/>
<TextBlock Text="Modern Windows Desktop UI" FontSize="11" Foreground="#ef4444" TextWrapping="Wrap"/>
</StackPanel>
</Border>
<Border Margin="4" Background="#f0f9ff" CornerRadius="8" Padding="14">
<StackPanel Spacing="6">
<TextBlock Text="🎮" FontSize="22"/>
<TextBlock Text="Unity UI" FontWeight="Bold" Foreground="#075985" FontSize="13"/>
<TextBlock Text="Game Engine User Interface" FontSize="11" Foreground="#0284c7" TextWrapping="Wrap"/>
</StackPanel>
</Border>
<Border Margin="4" Background="#f7fee7" CornerRadius="8" Padding="14">
<StackPanel Spacing="6">
<TextBlock Text="⚙️" FontSize="22"/>
<TextBlock Text="WinForms" FontWeight="Bold" Foreground="#365314" FontSize="13"/>
<TextBlock Text="Windows Forms (legacy)" FontSize="11" Foreground="#65a30d" TextWrapping="Wrap"/>
</StackPanel>
</Border>
<Border Margin="4" Background="#fefce8" CornerRadius="8" Padding="14">
<StackPanel Spacing="6">
<TextBlock Text="💻" FontSize="22"/>
<TextBlock Text="Electron" FontWeight="Bold" Foreground="#713f12" FontSize="13"/>
<TextBlock Text="Cross-platform desktop (JS)" FontSize="11" Foreground="#ca8a04" TextWrapping="Wrap"/>
</StackPanel>
</Border>
</WrapPanel>
</ScrollViewer>
<!-- ПОГАНО: Width/Height без Auto або * -->
<TextBox Width="240" Height="32"/>
<!-- ДОБРЕ: тільки Min/Max, сам елемент адаптується -->
<TextBox MinWidth="120" MaxWidth="400"/>
<!-- НОРМАЛЬНО: фіксований Width для sidebar (дизайн-рішення) -->
<Border Width="220"/> <!-- Sidebar фіксованої ширини — ок -->
Кожен рівень вкладання — додатковий Measure+Arrange Pass. При 10+ рівнях вкладання (що зустрічається у складних UI) — це виразно впливає на продуктивність.
<!-- ПОГАНО: надлишкове вкладання -->
<DockPanel>
<StackPanel>
<Border>
<Grid>
<StackPanel>
<TextBlock Text="Привіт"/>
</StackPanel>
</Grid>
</Border>
</StackPanel>
</DockPanel>
<!-- ДОБРЕ: тільки необхідні рівні -->
<DockPanel>
<Border Padding="16">
<TextBlock Text="Привіт"/>
</Border>
</DockPanel>
UseLayoutRounding="True" для чіткостіПри масштабуванні з fractional DPI (96, 120, 144 DPI) координати можуть бути дробовими. UseLayoutRounding вирівнює до цілих пікселів і позбавляє розмитих ліній:
<!-- На рівні вікна (поширюється на всіх дітей): -->
<Window UseLayoutRounding="True">
Або на конкретному елементі:
<Border UseLayoutRounding="True" BorderThickness="1" BorderBrush="#e2e8f0"/>
Без UseLayoutRounding="True" межі Border завтовшки 1px можуть виглядати 2px через SubPixel Rendering.
SnapsToDevicePixels для гострих ліній<!-- Для елементів де важлива чіткість (separator, border, chart lines): -->
<Rectangle SnapsToDevicePixels="True" Fill="#e2e8f0" Height="1"/>
SnapsToDevicePixels — старіший аналог UseLayoutRounding, впливає на рендеринг, а не на layout.
WPF застосовує обмеження у такому порядку (від вищого до нижчого пріоритету):
MinWidth ← MaxWidth ← Width ← DesiredSize (Auto/*)
Тобто MinWidth завжди перемагає над Width, а Width перемагає над Auto/DesiredSize. Це дозволяє будувати строгі обмеження:
<!-- Кнопка від 80px до 200px, бажано Auto -->
<Button MinWidth="80" MaxWidth="200" Width="Auto" Content="Адаптивна кнопка"/>
ScrollViewer?Золоте правило: якщо вміст може бути більшим за контейнер — загорніть у ScrollViewer.
<!-- Сторінка налаштувань — завжди у ScrollViewer: -->
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="24" Spacing="16">
<!-- Секції налаштувань -->
</StackPanel>
</ScrollViewer>
<!-- Навігаційна панель — теж у ScrollViewer: -->
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="4">
<!-- Пункти меню -->
</StackPanel>
</ScrollViewer>
Star vs Fixed
* і Auto замість фіксованих пікселів. Фіксовані розміри — тільки для Sidebar та UI-елементів де дизайн вимагає конкретного значення.Visibility
Collapsed — прибирає елемент з layout (responsive приховування). Hidden — зберігає місце (для валідаційних повідомлень). Ніколи не плутайте ці два режими.SizeChanged
Window.SizeChanged для breakpoint-логіки. Читайте e.NewSize.Width. Не читайте ActualWidth до Loaded.WrapPanel
WrapPanel + ItemWidth/ItemHeight + ScrollViewer. Переноситься автоматично. Ніякого C#.Завдання: Побудуйте форму реєстрації яка коректно виглядає при будь-якій ширині вікна від 320px до повного екрана.
Вимоги:
ScrollViewer + Grid MinWidth="320" — не зламується при вузьких вікнахGrid MaxWidth="480" HorizontalAlignment="Center" — на широких екранах не розтягуєтьсяGrid зі столонками Auto + * (мітки + поля)MinWidth="100" — завжди широкі достатньоUseLayoutRounding="True" на WindowТест: Звузьте вікно до 320px — форма скролиться, не ламається. Розширте до 1200px — форма по центру, не розтягується на весь екран.
Завдання: Dashboard з двома breakpoints через SizeChanged.
Layout: Grid з 3 колонками (Sidebar + Splitter + Content)
Breakpoints через SizeChanged:
width >= 900: sidebar видимий (220px), повна toolbar, всі карткиwidth 600-900: sidebar прихований (Collapsed), toolbar скорочена, 2 картки в рядкуwidth < 600: sidebar прихований, тільки іконки toolbar, 1 картка в рядкуРеалізація:
Window.SizeChanged у code-behindVisibility sidebar і splitterПеревірка: Resize вікна в реальному часі → sidebar плавно ховається/показується.
Завдання: Сторінка з адаптивними картками без єдиного рядка C# для адаптивності.
Layout (тільки XAML):
DockPanel → FilterBar (Top, WrapPanel з кнопками-фільтрами) + ScrollViewer (Fill)WrapPanel ItemWidth="220" ItemHeight="160" — 12+ картокBorder CornerRadius="10" з картинкою-emoji (ViewBox!), заголовком, описом, кнопкоюAdaptive поведінка:
Поглиблення: ScrollViewer навколо FilterBar + Cards, щоб увесь контент прокручувався. ViewBox всередині кожної картки для масштабування emoji-іконки.
Перевірка: Жодного C# коду для адаптивності. Resize вікна → WrapPanel перебудовує сітку автоматично.
Просунуті техніки Layout
Margin, Padding, Alignment, MinWidth/MaxWidth, ScrollViewer, ViewBox, Border, GridSplitter — повноцінний посібник з просунутих можливостей розташування елементів у WPF і Avalonia.
Layout в Avalonia: відмінності та нові можливості
Чим Avalonia відрізняється від WPF у layout: Spacing, RelativePanel, SplitView, Expander, ItemsRepeater, Transitions та інші можливості що роблять Avalonia потужнішим у сучасних сценаріях.