У попередній статті ми розібрали три панелі: StackPanel, WrapPanel, DockPanel. Всі вони вирішують конкретні завдання, але є принципово лінійними: елементи вишиковуються в один рядок, стовпець або прикріплюються до сторін.
Настав час головної панелі WPF. Якщо б вам довелося залишити тільки одну панель — це був би Grid. Він вирішує будь-яке завдання верстки: форми, діалоги, дашборди, складні сітки, повноекранні лейаути. Professional WPF-розробник використовує Grid у 70-80% випадків верстки.
*) — пропорційний розмір: решта місця поділяється між усіма *-рядками/колонками у їх пропорції. RowSpan / ColumnSpan — об'єднання кількох рядків/колонок для одного елемента. SharedSizeGroup — механізм синхронізації ширини колонок між різними Grid-ами. Canvas — панель абсолютного позиціонування: Canvas.Left/Top/Right/Bottom. UniformGrid — спрощений Grid з однаковими комірками.Grid — це таблиця. Ви визначаєте структуру рядків та колонок, а потім розміщуєте елементи у конкретних комірках. На відміну від HTML-таблиць, Grid у WPF не потребує вкладання <tr><td> — будь-який елемент може бути розміщений у будь-якій комірці через Attached Properties.
Навіть без оголошення RowDefinitions та ColumnDefinitions Grid вже є: він має 1 рядок і 1 колонку за замовчуванням. Тому будь-який <Grid> без визначень — це просто контейнер де всі елементи ставляться одне на одне по центру:
<!-- Один рядок, одна колонка — все по центру один на одному -->
<Grid>
<Rectangle Fill="#3b82f6" Width="200" Height="100"/>
<TextBlock Text="Поверх прямокутника" HorizontalAlignment="Center"
VerticalAlignment="Center" Foreground="White"/>
</Grid>
Це корисно коли вам потрібно накласти елементи один на одного (наприклад, Loading Spinner поверх контенту) — для цього Grid є кращим контейнером ніж Canvas.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- рядок по вмісту -->
<RowDefinition Height="*"/> <!-- рядок займає решту -->
<RowDefinition Height="60"/> <!-- рядок рівно 60px -->
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/> <!-- колонка рівно 200px -->
<ColumnDefinition Width="*"/> <!-- колонка займає решту -->
</Grid.ColumnDefinitions>
<!-- Тепер розміщуємо елементи через Grid.Row і Grid.Column -->
<TextBlock Grid.Row="0" Grid.Column="0" Text="Рядок 0, Колонка 0"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="Рядок 0, Колонка 1"/>
<ListBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"/>
<Button Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Content="OK"/>
</Grid>
Якщо Grid.Row або Grid.Column не вказані — за замовчуванням 0. Це корисно: перший елемент без атрибутів потрапляє у верхній лівий кут.
Це найважливіша концепція Grid. Кожен RowDefinition.Height та ColumnDefinition.Width може мати один з трьох типів розміру. Їх правильне поєднання — ключ до елегантного layout.
Просто число — означає конкретну кількість device-independent pixels (DIP). Рядок або колонка завжди цього розміру, незалежно від вмісту чи розміру вікна:
<ColumnDefinition Width="200"/> <!-- завжди 200px -->
<RowDefinition Height="48"/> <!-- завжди 48px -->
Коли використовувати: Sidebar фіксованої ширини, header фіксованої висоти, toolbar. Загалом — коли розмір не має змінюватися з вмістом.
Коли НЕ використовувати: Основний вміст, форми де текст може бути різної довжини (через локалізацію тощо).
Auto — The Grid просить дочірній елемент визначити потрібний розмір (Measure Pass), і потім встановлює розмір рядка/колонки рівним максимальному DesiredSize серед усіх елементів у цьому рядку/колонці:
<ColumnDefinition Width="Auto"/> <!-- ширина = ширина найшироішого елемента у цій колонці -->
<RowDefinition Height="Auto"/> <!-- висота = висота найвищого елемента у цьому рядку -->
Коли використовувати: Label-колонки у формах (мітки різної довжини — Auto підлаштовується), рядки з контентом різної висоти.
Важливо: Auto-колонка або рядок ніколи не займе більше ніж треба. Але й не менше.
<!-- Класичний Layout форми: Label Auto, Input * -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/> <!-- Колонка міток -->
<ColumnDefinition Width="*"/> <!-- Колонка полів вводу -->
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="Ім'я:"/>
<TextBox Grid.Row="0" Grid.Column="1" Margin="4"/>
<Label Grid.Row="1" Grid.Column="0" Content="Email:"/>
<TextBox Grid.Row="1" Grid.Column="1" Margin="4"/>
<Label Grid.Row="2" Grid.Column="0" Content="Телефон:"/>
<TextBox Grid.Row="2" Grid.Column="1" Margin="4"/>
</Grid>
Мітка "Телефон:" трохи довша за "Ім'я:" та "Email:" — але тому що перша колонка Auto, вся колонка автоматично розшириться до ширини найдовшої мітки. Всі поля вводу (у *-колонці) при цьому залишаться вирівняними.
Зірочкові рядки/колонки ділять між собою весь залишковий простір після фіксованих і Auto-розмірів. За замовчуванням зірочки рівноправні:
<Grid Width="300">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <!-- 150px: половина -->
<ColumnDefinition Width="*"/> <!-- 150px: половина -->
</Grid.ColumnDefinitions>
</Grid>
Числовий коефіцієнт перед зірочкою задає пропорцію. 2* означає "вдвічі більше ніж 1*":
<Grid Width="600">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <!-- 150px: 1 частина з 4 -->
<ColumnDefinition Width="2*"/> <!-- 300px: 2 частини з 4 -->
<ColumnDefinition Width="*"/> <!-- 150px: 1 частина з 4 -->
</Grid.ColumnDefinitions>
<!-- Загалом: 1+2+1 = 4 частини. 600 / 4 = 150px на частину -->
</Grid>
Алгоритм:
*-визначень<!-- Приклад: Total width = 800px -->
<Grid Width="800">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/> <!-- fixed: 200px -->
<ColumnDefinition Width="Auto"/> <!-- auto: припустимо 80px (по вмісту) -->
<ColumnDefinition Width="2*"/> <!-- star: (800-200-80) * 2/3 = 346.67px -->
<ColumnDefinition Width="*"/> <!-- star: (800-200-80) * 1/3 = 173.33px -->
</Grid.ColumnDefinitions>
</Grid>
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<Grid Margin="16" ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="60"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<!-- Рядок 0 (Auto) -->
<Border Grid.Row="0" Grid.Column="0" Background="#fef3c7" Padding="8,4">
<TextBlock Text="Auto Col" FontWeight="Bold" FontSize="11"/>
</Border>
<Border Grid.Row="0" Grid.Column="1" Background="#fef3c7" Padding="8,4">
<TextBlock Text="1* Col" FontWeight="Bold" FontSize="11"/>
</Border>
<Border Grid.Row="0" Grid.Column="2" Background="#fef3c7" Padding="8,4">
<TextBlock Text="2* Col" FontWeight="Bold" FontSize="11"/>
</Border>
<!-- Рядок 1 (1*) -->
<Border Grid.Row="1" Grid.Column="0" Background="#dbeafe" Padding="4">
<TextBlock Text="Auto" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="11"/>
</Border>
<Border Grid.Row="1" Grid.Column="1" Background="#e0f2fe" Padding="4">
<TextBlock Text="1* Row" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="11"/>
</Border>
<Border Grid.Row="1" Grid.Column="2" Background="#bae6fd" Padding="4">
<TextBlock Text="2* Column" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="11"/>
</Border>
<!-- Рядок 2 (2*) -->
<Border Grid.Row="2" Grid.Column="0" Background="#dcfce7" Padding="4">
<TextBlock Text="Auto" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="11"/>
</Border>
<Border Grid.Row="2" Grid.Column="1" Background="#bbf7d0" Padding="4">
<TextBlock Text="2* Row" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="11"/>
</Border>
<Border Grid.Row="2" Grid.Column="2" Background="#86efac" Padding="4">
<TextBlock Text="2*✕2*" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="11"/>
</Border>
<!-- Рядок 3 (60px fixed) -->
<Border Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3" Background="#fce7f3" Padding="4">
<TextBlock Text="60px fixed row (ColumnSpan=3)" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="11"/>
</Border>
</Grid>
ShowGridLines="True" для відлагодженняShowGridLines — корисний атрибут при розробці: він малює пунктирні лінії між рядками та колонками. У готовому застосунку завжди видаляйте або ставте False.
<!-- Тільки для розробки: -->
<Grid ShowGridLines="True">
Без Attached Properties Grid.Row і Grid.Column усі дочірні елементи потрапляють у комірку [0, 0]. Значення індексовані з нуля:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- Row 0 -->
<RowDefinition Height="Auto"/> <!-- Row 1 -->
<RowDefinition Height="*"/> <!-- Row 2 -->
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/> <!-- Column 0 -->
<ColumnDefinition Width="*"/> <!-- Column 1 -->
<ColumnDefinition Width="Auto"/> <!-- Column 2 -->
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Ім'я:"/>
<TextBox Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Країна:"/>
<ComboBox Grid.Row="1" Grid.Column="1"/>
<Button Grid.Row="1" Grid.Column="2" Content="..."/>
<TextBox Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3"
AcceptsReturn="True" TextWrapping="Wrap"/>
</Grid>
Grid не має вбудованого Gap між комірками (на відміну від CSS Grid). Відступи задаються Margin на дочірньому елементі:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Email:" VerticalAlignment="Center"
Margin="0,0,8,0"/> <!-- 8px правого відступу між міткою і полем -->
<TextBox Grid.Column="1" Margin="0,4"/> <!-- 4px зверху і знизу -->
</Grid>
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<Border Background="White" Padding="28" CornerRadius="10" Margin="20"
BorderBrush="#e2e8f0" BorderThickness="1">
<Grid Width="320">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="24"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="12"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="20"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Text="Вхід до системи" FontSize="20" FontWeight="Bold"
Foreground="#1e293b"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Email:"
VerticalAlignment="Center" FontWeight="SemiBold"
Foreground="#475569" Margin="0,0,12,0"/>
<TextBox Grid.Row="2" Grid.Column="1" Text="ivan@example.com"
Padding="10,7" BorderBrush="#cbd5e1" BorderThickness="1"/>
<TextBlock Grid.Row="4" Grid.Column="0" Text="Пароль:"
VerticalAlignment="Center" FontWeight="SemiBold"
Foreground="#475569" Margin="0,0,12,0"/>
<PasswordBox Grid.Row="4" Grid.Column="1"
Padding="10,7" BorderBrush="#cbd5e1" BorderThickness="1"/>
<StackPanel Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2"
Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8">
<Button Content="Скасувати" Padding="16,8"/>
<Button Content="Увійти" Padding="20,8"
Background="#2563eb" Foreground="White" FontWeight="SemiBold"/>
</StackPanel>
</Grid>
</Border>
Grid.RowSpan та Grid.ColumnSpan дозволяють елементу займати кілька суміжних рядків або колонок:
<Button Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Content="Розтягнута кнопка"/>
<ListBox Grid.Row="0" Grid.Column="0" Grid.RowSpan="3"/>
<Image Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="2"/>
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<Grid Margin="16" Width="480" Height="320">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Велика картка: 2 рядки × 2 колонки -->
<Border Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2"
Background="#1e40af" CornerRadius="8" Margin="4">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="4">
<TextBlock Text="📊" FontSize="36" HorizontalAlignment="Center"/>
<TextBlock Text="Загальний дохід" Foreground="#93c5fd" FontSize="12" HorizontalAlignment="Center"/>
<TextBlock Text="₴ 847,230" FontSize="24" FontWeight="Bold"
Foreground="White" HorizontalAlignment="Center"/>
<TextBlock Text="+12.5% цього місяця" Foreground="#86efac" FontSize="11" HorizontalAlignment="Center"/>
</StackPanel>
</Border>
<Border Grid.Row="0" Grid.Column="2" Background="#059669" CornerRadius="8" Margin="4">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="2">
<TextBlock Text="👥" FontSize="20" HorizontalAlignment="Center"/>
<TextBlock Text="Користувачі" Foreground="#a7f3d0" FontSize="10" HorizontalAlignment="Center"/>
<TextBlock Text="3,421" FontSize="18" FontWeight="Bold" Foreground="White" HorizontalAlignment="Center"/>
</StackPanel>
</Border>
<Border Grid.Row="1" Grid.Column="2" Background="#7c3aed" CornerRadius="8" Margin="4">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="2">
<TextBlock Text="📦" FontSize="20" HorizontalAlignment="Center"/>
<TextBlock Text="Замовлення" Foreground="#ddd6fe" FontSize="10" HorizontalAlignment="Center"/>
<TextBlock Text="1,847" FontSize="18" FontWeight="Bold" Foreground="White" HorizontalAlignment="Center"/>
</StackPanel>
</Border>
<Border Grid.Row="2" Grid.Column="0" Background="#0f172a" CornerRadius="8" Margin="4">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="Конверсія" Foreground="#94a3b8" FontSize="10" HorizontalAlignment="Center"/>
<TextBlock Text="4.7%" FontSize="16" FontWeight="Bold" Foreground="White" HorizontalAlignment="Center"/>
</StackPanel>
</Border>
<Border Grid.Row="2" Grid.Column="1" Background="#0f172a" CornerRadius="8" Margin="4">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="Avg. Order" Foreground="#94a3b8" FontSize="10" HorizontalAlignment="Center"/>
<TextBlock Text="₴ 458" FontSize="16" FontWeight="Bold" Foreground="White" HorizontalAlignment="Center"/>
</StackPanel>
</Border>
<Border Grid.Row="2" Grid.Column="2" Background="#0f172a" CornerRadius="8" Margin="4">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="Повернення" Foreground="#94a3b8" FontSize="10" HorizontalAlignment="Center"/>
<TextBlock Text="2.1%" FontSize="16" FontWeight="Bold" Foreground="White" HorizontalAlignment="Center"/>
</StackPanel>
</Border>
</Grid>
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<Border Background="#1c1c1e" CornerRadius="12" Margin="20" Width="280">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="80"/>
<RowDefinition Height="8"/>
<RowDefinition Height="56"/>
<RowDefinition Height="56"/>
<RowDefinition Height="56"/>
<RowDefinition Height="56"/>
<RowDefinition Height="56"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="4"
Background="#2c2c2e" CornerRadius="8" Padding="12,10">
<StackPanel VerticalAlignment="Bottom">
<TextBlock Text="123 + 456" Foreground="#8e8e93" FontSize="13" HorizontalAlignment="Right"/>
<TextBlock Text="579" Foreground="White" FontSize="36" FontWeight="Light" HorizontalAlignment="Right"/>
</StackPanel>
</Border>
<Button Grid.Row="2" Grid.Column="0" Content="AC" Margin="3" Padding="0,12" Background="#a1a1aa" Foreground="Black" FontSize="16" FontWeight="SemiBold"/>
<Button Grid.Row="2" Grid.Column="1" Content="+/-" Margin="3" Padding="0,12" Background="#a1a1aa" Foreground="Black" FontSize="16" FontWeight="SemiBold"/>
<Button Grid.Row="2" Grid.Column="2" Content="%" Margin="3" Padding="0,12" Background="#a1a1aa" Foreground="Black" FontSize="16" FontWeight="SemiBold"/>
<Button Grid.Row="2" Grid.Column="3" Content="÷" Margin="3" Padding="0,12" Background="#f59e0b" Foreground="White" FontSize="20" FontWeight="Bold"/>
<Button Grid.Row="3" Grid.Column="0" Content="7" Margin="3" Padding="0,12" Background="#3a3a3c" Foreground="White" FontSize="18"/>
<Button Grid.Row="3" Grid.Column="1" Content="8" Margin="3" Padding="0,12" Background="#3a3a3c" Foreground="White" FontSize="18"/>
<Button Grid.Row="3" Grid.Column="2" Content="9" Margin="3" Padding="0,12" Background="#3a3a3c" Foreground="White" FontSize="18"/>
<Button Grid.Row="3" Grid.Column="3" Content="×" Margin="3" Padding="0,12" Background="#f59e0b" Foreground="White" FontSize="20" FontWeight="Bold"/>
<Button Grid.Row="4" Grid.Column="0" Content="4" Margin="3" Padding="0,12" Background="#3a3a3c" Foreground="White" FontSize="18"/>
<Button Grid.Row="4" Grid.Column="1" Content="5" Margin="3" Padding="0,12" Background="#3a3a3c" Foreground="White" FontSize="18"/>
<Button Grid.Row="4" Grid.Column="2" Content="6" Margin="3" Padding="0,12" Background="#3a3a3c" Foreground="White" FontSize="18"/>
<Button Grid.Row="4" Grid.Column="3" Content="-" Margin="3" Padding="0,12" Background="#f59e0b" Foreground="White" FontSize="20" FontWeight="Bold"/>
<Button Grid.Row="5" Grid.Column="0" Content="1" Margin="3" Padding="0,12" Background="#3a3a3c" Foreground="White" FontSize="18"/>
<Button Grid.Row="5" Grid.Column="1" Content="2" Margin="3" Padding="0,12" Background="#3a3a3c" Foreground="White" FontSize="18"/>
<Button Grid.Row="5" Grid.Column="2" Content="3" Margin="3" Padding="0,12" Background="#3a3a3c" Foreground="White" FontSize="18"/>
<Button Grid.Row="5" Grid.Column="3" Content="+" Margin="3" Padding="0,12" Background="#f59e0b" Foreground="White" FontSize="20" FontWeight="Bold"/>
<Button Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2" Content="0" Margin="3"
Background="#3a3a3c" Foreground="White" FontSize="18"
HorizontalContentAlignment="Left" Padding="20,12,0,12"/>
<Button Grid.Row="6" Grid.Column="2" Content="." Margin="3" Padding="0,12" Background="#3a3a3c" Foreground="White" FontSize="18"/>
<Button Grid.Row="6" Grid.Column="3" Content="=" Margin="3" Padding="0,12" Background="#f59e0b" Foreground="White" FontSize="20" FontWeight="Bold"/>
</Grid>
</Border>
Уявіть список налаштувань де кожен рядок — окремий маленький Grid з міткою і полем вводу. Мітки мають різну ширину (бо Auto), але ви хочете щоб усі вони вирівнялись — як у таблиці.
SharedSizeGroup дозволяє колонкам у різних Grid ділити спільну ширину.
<!-- Grid.IsSharedSizeScope="True" на батьківському контейнері -->
<StackPanel Grid.IsSharedSizeScope="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelColumn"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Ім'я:"/>
<TextBox Grid.Column="1"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelColumn"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Ця мітка довша — але ширина першої колонки синхронізується між обома Grid -->
<TextBlock Grid.Column="0" Text="Довга назва поля:"/>
<TextBox Grid.Column="1"/>
</Grid>
</StackPanel>
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<StackPanel Grid.IsSharedSizeScope="True" Margin="20" Spacing="0">
<Border Background="#f8fafc" Padding="16" Margin="0,0,0,1">
<StackPanel Spacing="8">
<TextBlock Text="ОСОБИСТІ ДАНІ" FontSize="11" FontWeight="Bold" Foreground="#94a3b8"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="FormLabel"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Ім'я та прізвище:" Margin="0,0,12,0"
VerticalAlignment="Center" Foreground="#475569"/>
<TextBox Grid.Column="1" Text="Іван Петренко" Padding="8,5"
BorderBrush="#e2e8f0" BorderThickness="1"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="FormLabel"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Дата народження:" Margin="0,0,12,0"
VerticalAlignment="Center" Foreground="#475569"/>
<TextBox Grid.Column="1" Text="01.01.1990" Padding="8,5"
BorderBrush="#e2e8f0" BorderThickness="1"/>
</Grid>
</StackPanel>
</Border>
<Border Background="White" Padding="16" Margin="0,0,0,1">
<StackPanel Spacing="8">
<TextBlock Text="КОНТАКТНІ ДАНІ" FontSize="11" FontWeight="Bold" Foreground="#94a3b8"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="FormLabel"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Email:" Margin="0,0,12,0"
VerticalAlignment="Center" Foreground="#475569"/>
<TextBox Grid.Column="1" Text="ivan@example.com" Padding="8,5"
BorderBrush="#e2e8f0" BorderThickness="1"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="FormLabel"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Контактний телефон:" Margin="0,0,12,0"
VerticalAlignment="Center" Foreground="#475569"/>
<TextBox Grid.Column="1" Text="+380 (67) 123-45-67" Padding="8,5"
BorderBrush="#e2e8f0" BorderThickness="1"/>
</Grid>
</StackPanel>
</Border>
</StackPanel>
Grid.IsSharedSizeScope="True" встановлюється на батьківський контейнер (StackPanel, ScrollViewer тощо) — не на самі Grid. Назва SharedSizeGroup — довільний рядок; головне щоб він збігався у всіх колонках що мають синхронізуватися.Canvas — це панель де ви самі вказуєте точну позицію кожного елемента у пікселях. Вона принципово відрізняється від усіх попередніх панелей.
<Canvas>
<Button Canvas.Left="50" Canvas.Top="20" Content="Кнопка 1"/>
<Button Canvas.Left="200" Canvas.Top="80" Content="Кнопка 2"/>
<Button Canvas.Right="20" Canvas.Bottom="20" Content="Правий низ"/>
</Canvas>
Чотири Attached Properties для позиціонування:
Canvas.Left — відстань від лівого краю CanvasCanvas.Top — відстань від верхнього краю CanvasCanvas.Right — відстань від правого краю (потребує відомих розмірів Canvas)Canvas.Bottom — відстань від нижнього краюВажливо: Canvas.Left і Canvas.Right не взаємовиключають — якщо задані обидва, а ширина Canvas відома, елемент буде розтягнутий. Зазвичай використовують або Left+Top, або Right+Bottom.
Canvas виглядає привабливо для початківців — "задам координати і готово". Але у реальних застосунках це антипатерн:
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<Canvas Width="400" Height="260" Background="#f8fafc" Margin="16">
<!-- Перший файл (обраний) -->
<Border Canvas.Left="20" Canvas.Top="20" Width="80" Height="90"
Background="#dbeafe" CornerRadius="8"
BorderBrush="#93c5fd" BorderThickness="2">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="4">
<TextBlock Text="📄" FontSize="28" HorizontalAlignment="Center"/>
<TextBlock Text="report.pdf" FontSize="9" HorizontalAlignment="Center"
Foreground="#1e40af" TextTrimming="CharacterEllipsis"/>
</StackPanel>
</Border>
<!-- Другий файл -->
<Border Canvas.Left="120" Canvas.Top="20" Width="80" Height="90"
Background="White" CornerRadius="8"
BorderBrush="#e2e8f0" BorderThickness="1">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="4">
<TextBlock Text="🖼️" FontSize="28" HorizontalAlignment="Center"/>
<TextBlock Text="photo.jpg" FontSize="9" HorizontalAlignment="Center"
Foreground="#475569" TextTrimming="CharacterEllipsis"/>
</StackPanel>
</Border>
<!-- Третій файл -->
<Border Canvas.Left="220" Canvas.Top="20" Width="80" Height="90"
Background="White" CornerRadius="8"
BorderBrush="#e2e8f0" BorderThickness="1">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="4">
<TextBlock Text="📊" FontSize="28" HorizontalAlignment="Center"/>
<TextBlock Text="data.xlsx" FontSize="9" HorizontalAlignment="Center"
Foreground="#475569" TextTrimming="CharacterEllipsis"/>
</StackPanel>
</Border>
<!-- Перекриваючий елемент: "виділення" рамкою — просто ще один елемент з Z-order -->
<Border Canvas.Left="316" Canvas.Top="20" Width="80" Height="90"
Background="White" CornerRadius="8"
BorderBrush="#e2e8f0" BorderThickness="1">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="4">
<TextBlock Text="🎵" FontSize="28" HorizontalAlignment="Center"/>
<TextBlock Text="audio.mp3" FontSize="9" HorizontalAlignment="Center"
Foreground="#475569" TextTrimming="CharacterEllipsis"/>
</StackPanel>
</Border>
<!-- Контекстне меню що "пливе" -->
<Border Canvas.Left="80" Canvas.Top="120"
Background="White" CornerRadius="6" Padding="4"
BorderBrush="#e2e8f0" BorderThickness="1">
<StackPanel Spacing="0" Width="160">
<Button Content="📂 Відкрити" HorizontalContentAlignment="Left" Padding="10,6" Background="Transparent" FontSize="12"/>
<Button Content="✂️ Вирізати" HorizontalContentAlignment="Left" Padding="10,6" Background="Transparent" FontSize="12"/>
<Button Content="📋 Копіювати" HorizontalContentAlignment="Left" Padding="10,6" Background="Transparent" FontSize="12"/>
<Separator Margin="4,2"/>
<Button Content="🗑️ Видалити" HorizontalContentAlignment="Left" Padding="10,6" Background="Transparent" FontSize="12" Foreground="#dc2626"/>
</StackPanel>
</Border>
<!-- Tooltip/Badge що накладається -->
<Border Canvas.Left="80" Canvas.Top="16" Background="#ef4444" CornerRadius="10" Padding="5,2">
<TextBlock Text="Вибрано" Foreground="White" FontSize="10"/>
</Border>
</Canvas>
Canvas є правильним інструментом у специфічних сценаріях:
Line, Ellipse, Polygon — малювання фігур по координатах<!-- Canvas для простої векторної графіки -->
<Canvas Width="300" Height="200">
<Line X1="0" Y1="100" X2="300" Y2="100" Stroke="Gray" StrokeThickness="1"/>
<Line X1="150" Y1="0" X2="150" Y2="200" Stroke="Gray" StrokeThickness="1"/>
<Ellipse Canvas.Left="50" Canvas.Top="50" Width="80" Height="80"
Fill="#3b82f6" Opacity="0.7"/>
<Ellipse Canvas.Left="120" Canvas.Top="70" Width="100" Height="100"
Fill="#ef4444" Opacity="0.5"/>
</Canvas>
Коли елементи перекриваються — Panel.ZIndex визначає хто зверху. Більше значення = поверх:
<Canvas>
<Rectangle Canvas.Left="20" Canvas.Top="20" Width="100" Height="100"
Fill="Blue" Panel.ZIndex="1"/>
<Rectangle Canvas.Left="60" Canvas.Top="60" Width="100" Height="100"
Fill="Red" Panel.ZIndex="2"/> <!-- Червоний поверх синього -->
</Canvas>
З-Index також працює в Grid при накладенні елементів.
UniformGrid — спрощений варіант Grid де всі комірки однакового розміру. Ніяких RowDefinitions, ніяких Grid.Row атрибутів — елементи заповнюються зліва направо, рядок за рядком автоматично.
<!-- Задаємо тільки кількість рядків або колонок -->
<UniformGrid Rows="2" Columns="3">
<Button Content="1"/>
<Button Content="2"/>
<Button Content="3"/>
<!-- Автоматично переноситься на рядок 2 -->
<Button Content="4"/>
<Button Content="5"/>
<Button Content="6"/>
</UniformGrid>
Rows і ColumnsColumns — кількість рядків визначається автоматично по кількості елементівRows — кількість колонок визначається автоматично<!-- 3 колонки, кількість рядків = ceil(n / 3) -->
<UniformGrid Columns="3">
<Button Content="A"/>
<Button Content="B"/>
<Button Content="C"/>
<Button Content="D"/>
<Button Content="E"/>
<!-- 5 елементів / 3 = 2 рядки (6 комірок, одна порожня) -->
</UniformGrid>
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<UniformGrid Rows="4" Columns="3" Margin="20" Width="200">
<Button Content="7" Margin="3" Padding="0,12" FontSize="18" Background="#f1f5f9"/>
<Button Content="8" Margin="3" Padding="0,12" FontSize="18" Background="#f1f5f9"/>
<Button Content="9" Margin="3" Padding="0,12" FontSize="18" Background="#f1f5f9"/>
<Button Content="4" Margin="3" Padding="0,12" FontSize="18" Background="#f1f5f9"/>
<Button Content="5" Margin="3" Padding="0,12" FontSize="18" Background="#f1f5f9"/>
<Button Content="6" Margin="3" Padding="0,12" FontSize="18" Background="#f1f5f9"/>
<Button Content="1" Margin="3" Padding="0,12" FontSize="18" Background="#f1f5f9"/>
<Button Content="2" Margin="3" Padding="0,12" FontSize="18" Background="#f1f5f9"/>
<Button Content="3" Margin="3" Padding="0,12" FontSize="18" Background="#f1f5f9"/>
<Button Content="←" Margin="3" Padding="0,12" FontSize="18" Background="#fecaca" Foreground="#dc2626"/>
<Button Content="0" Margin="3" Padding="0,12" FontSize="18" Background="#f1f5f9"/>
<Button Content="↵" Margin="3" Padding="0,12" FontSize="18" Background="#bbf7d0" Foreground="#16a34a" FontWeight="Bold"/>
</UniformGrid>
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<UniformGrid Columns="3" Margin="16">
<Border Margin="6" Background="#eff6ff" CornerRadius="8" Padding="16">
<StackPanel Spacing="6">
<TextBlock Text="⚡" FontSize="24"/>
<TextBlock Text="Швидкість" FontWeight="SemiBold" Foreground="#1e40af"/>
<TextBlock Text="SkiaSharp рендеринг на всіх платформах" FontSize="11" Foreground="#3b82f6" TextWrapping="Wrap"/>
</StackPanel>
</Border>
<Border Margin="6" Background="#f0fdf4" CornerRadius="8" Padding="16">
<StackPanel Spacing="6">
<TextBlock Text="🌐" FontSize="24"/>
<TextBlock Text="Кросплатформність" FontWeight="SemiBold" Foreground="#166534"/>
<TextBlock Text="Windows, macOS, Linux, iOS, Android" FontSize="11" Foreground="#16a34a" TextWrapping="Wrap"/>
</StackPanel>
</Border>
<Border Margin="6" Background="#fdf4ff" CornerRadius="8" Padding="16">
<StackPanel Spacing="6">
<TextBlock Text="🎨" FontSize="24"/>
<TextBlock Text="Стилізація" FontWeight="SemiBold" Foreground="#6b21a8"/>
<TextBlock Text="CSS-подібні стилі, анімації, теми" FontSize="11" Foreground="#8b5cf6" TextWrapping="Wrap"/>
</StackPanel>
</Border>
<Border Margin="6" Background="#fff7ed" CornerRadius="8" Padding="16">
<StackPanel Spacing="6">
<TextBlock Text="🔗" FontSize="24"/>
<TextBlock Text="Data Binding" FontWeight="SemiBold" Foreground="#9a3412"/>
<TextBlock Text="Compiled Bindings — швидкі та type-safe" FontSize="11" Foreground="#ea580c" TextWrapping="Wrap"/>
</StackPanel>
</Border>
<Border Margin="6" Background="#fef2f2" CornerRadius="8" Padding="16">
<StackPanel Spacing="6">
<TextBlock Text="🧩" FontSize="24"/>
<TextBlock Text="Контроли" FontWeight="SemiBold" Foreground="#991b1b"/>
<TextBlock Text="Багата бібліотека вбудованих контролів" FontSize="11" Foreground="#ef4444" TextWrapping="Wrap"/>
</StackPanel>
</Border>
<Border Margin="6" Background="#f0f9ff" CornerRadius="8" Padding="16">
<StackPanel Spacing="6">
<TextBlock Text="💡" FontSize="24"/>
<TextBlock Text="MVVM" FontWeight="SemiBold" Foreground="#075985"/>
<TextBlock Text="Перша підтримка MVVM з реактивними командами" FontSize="11" Foreground="#0284c7" TextWrapping="Wrap"/>
</StackPanel>
</Border>
</UniformGrid>
| Ситуація | Рекомендація |
|---|---|
| Всі комірки однакового розміру | UniformGrid — простіше |
| Різні розміри рядків/колонок | Grid |
Auto або * розміри | Grid |
| RowSpan / ColumnSpan | Grid |
| Заповнення по кількості | UniformGrid Columns="N" |
| SharedSizeGroup між Grid-ами | Grid |
Grid
Canvas
Canvas.Left/Top/Right/Bottom.UniformGrid
RowDefinitions, без Grid.Row атрибутів. Елементи заповнюються автоматично. Rows і/або Columns. Для клавіатур, карток, іконок.Завдання: Реалізуйте калькулятор у стилі iOS через Grid.
Структура Grid:
*-колонкиОбов'язкові елементи:
Grid.ColumnSpan="4" — відображає поточний вираз0 (Grid.ColumnSpan="2"), . та = (рядок 6)Перевірка: При запуску вікно виглядає точно як iOS калькулятор (пропорції можуть відрізнятися).
Завдання: Форма з двома секціями, де мітки вирівняні між секціями через SharedSizeGroup.
Секція 1 "Акаунт": Email + Пароль + Підтвердження пароля
Секція 2 "Профіль": Ім'я та прізвище + Телефон + Дата народження + Країна (ComboBox)
Вимоги:
Auto SharedSizeGroup="FormLabel" + *StackPanel Grid.IsSharedSizeScope="True"Перевірка: Найдовша мітка визначає ширину мітчої колонки для обох секцій.
Завдання: Дашборд аналітики подібний до прикладу, але складніший.
Layout outer: DockPanel — Sidebar (ліво, 200px) + Header (верх, 60px) + Content (Fill)
Layout content (3×4 Grid з автоматичними рядками і *-колонками):
RowSpan=2, ColumnSpan=2 (верхній лівий блок)RowSpan=1 кожнаUniformGrid Columns="3" з 6 карток "Топ продукти"Canvas: Використайте Canvas для невеликого "notification badge" — червоне коло поверх іконки у Sidebar (задіяти знання про Z-Index та накладення).
Перевірка: Dashboard адаптується по ширині (всі *-колонки). Sidebar фіксований. Badge "пливе" поверх іконки.
Панелі Layout: StackPanel, WrapPanel, DockPanel
Вивчаємо основні панелі розташування WPF: двопрохідна модель Measure/Arrange, StackPanel для лінійного вкладання, WrapPanel для автоматичного переносу та DockPanel для класичного інтерфейсу.
Просунуті техніки Layout
Margin, Padding, Alignment, MinWidth/MaxWidth, ScrollViewer, ViewBox, Border, GridSplitter — повноцінний посібник з просунутих можливостей розташування елементів у WPF і Avalonia.