ContentControl, ItemsControl, HeaderedContentControl, GroupBox, Expander, ExpandDirection, TabControl, TabItem, StatusBar, StatusBarItem, Content Model, Logical Tree, Visual Tree.Коли новачок у WPF вперше бачить, що Button може містити StackPanel з Image та TextBlock, — він здивований. У WinForms кнопка — це кнопка: прямокутник із написом. Звідки така гнучкість?
Відповідь — у Content Model (Моделі Вмісту), архітектурному рішенні, яке пронизує весь WPF від самого фундаменту. Розуміння цієї концепції є ключем не просто до групових контролів цієї статті, а й до всього WPF загалом — до DataTemplate, ControlTemplate, стилізації та навіть до MVVM.
У WPF кожен контрол визначає, скількох і яких дочірніх елементів він очікує. Виходячи з цього, все дерево контролів розбивається на дві великі категорії:
ContentControl — клас, для якого вміст означає рівно один об'єкт, але цей об'єкт може бути будь-чим. Властивість Content типу object приймає:
TextBlock автоматично;UIElement — відображається як є;UIElement) — відображається через DataTemplate або ToString().Це означає: якщо Button є ContentControl (а він є), то його Content може бути StackPanel, що містить Image і TextBlock. WPF просто покладає цей StackPanel всередину кнопки і відображає — без жодних обмежень. Саме тому у WPF немає окремого класу ImageButton: будь-яка Button вже є "image button", якщо в неї покласти Image.
Приклади ContentControl-ів у WPF:
| Клас | Де живе Content |
|---|---|
Button, RepeatButton | Видима область кнопки |
Label | Відображувана частина підпису |
CheckBox, RadioButton | Текст праворуч від позначки |
GroupBox | Вміст рамки |
Expander | Вміст, що розкривається |
TabItem | Вміст відповідної вкладки |
ScrollViewer | Прокручуваний вміст |
Window | Єдиний кореневий елемент вікна |
ItemsControl — клас для контролів, що відображають колекцію елементів. Замість одного Content — властивості Items (колекція ItemCollection) та ItemsSource (прив'язка до зовнішньої колекції).
Приклади ItemsControl-ів:
| Клас | Що містить |
|---|---|
ListBox, ListView | Список рядків або складних елементів |
ComboBox | Список варіантів для вибору |
Menu, ContextMenu | Пункти меню |
TabControl | Колекція TabItem-ів |
StatusBar | Колекція StatusBarItem-ів |
TreeView | Ієрархічне дерево вузлів |
ContentControl прив'язується до одного об'єкта (DataContext). ItemsControl прив'язується до колекції (ItemsSource). Розуміння цієї різниці є передумовою для Data Binding у Блоці 6.Між ContentControl і конкретними контролами на кшталт GroupBox або Expander стоїть ще один проміжний клас — HeaderedContentControl. Він розширює ContentControl, додаючи друге "місце" для вмісту — Header. Ієрархія:
FrameworkElement
└── Control
└── ContentControl
└── HeaderedContentControl
├── GroupBox
└── Expander
Header — так само object, як і Content. Це означає: заголовок GroupBox або стрілка-заголовок Expander можуть містити не лише текст, а й зображення, іконку, кнопку або цілий StackPanel. Саме таку гнучкість ми побачимо у прикладах нижче.
GroupBox — найнаочніший простий HeaderedContentControl. Він малює видиму рамку (border) навколо свого вмісту і виводить заголовок (Header) у лівій частині верхньої межі рамки. Це класичний UI-паттерн для логічного групування пов'язаних елементів у формах: "Персональні дані", "Адреса", "Налаштування безпеки".
Важливо розуміти: GroupBox — це виключно візуальний та семантичний контейнер. Він не впливає на логіку, не обмежує введення, не об'єднує RadioButton-и в групу (для цього використовується GroupName). Його єдина роль — дати користувачу зрозуміти, що ці елементи пов'язані між собою.
Типова структура GroupBox:
<GroupBox Header="Заголовок групи">
<!-- Content: будь-який один UIElement -->
<StackPanel>
<!-- Довільна кількість дочірніх елементів -->
</StackPanel>
</GroupBox>
Оскільки Content приймає лише один дочірній елемент, завжди потрібен контейнер-посередник (StackPanel, Grid, WrapPanel) для розміщення кількох полів всередині GroupBox.
Одна з малопомічних, але потужних можливостей — Header може бути не просто рядком, а повноцінним XAML-елементом. Це дозволяє створювати заголовки з іконками, кольоровим текстом або навіть функціональними елементами (наприклад, CheckBox у заголовку для вмикання/вимикання всієї групи — патерн "Enable Group"):
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<StackPanel Margin="20" Spacing="16">
<!-- Звичайний рядковий заголовок -->
<GroupBox Header="👤 Персональні дані">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="8"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Ім'я:"
VerticalAlignment="Center" Foreground="Gray"/>
<TextBox Grid.Row="0" Grid.Column="1" Padding="6,4"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Прізвище:"
VerticalAlignment="Center" Foreground="Gray"/>
<TextBox Grid.Row="2" Grid.Column="1" Padding="6,4"/>
</Grid>
</GroupBox>
<!-- XAML-заголовок: TextBlock із кольором та шрифтом -->
<GroupBox>
<GroupBox.Header>
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock Text="🏠" FontSize="14"/>
<TextBlock Text="Адреса доставки"
Foreground="#6366F1"
FontWeight="SemiBold"/>
</StackPanel>
</GroupBox.Header>
<StackPanel Margin="8" Spacing="8">
<TextBox PlaceholderText="Вулиця, будинок, квартира" Padding="6,4"/>
<TextBox PlaceholderText="Місто" Padding="6,4"/>
<TextBox PlaceholderText="Поштовий індекс" MaxLength="5" Padding="6,4"/>
</StackPanel>
</GroupBox>
</StackPanel>
Один з найпоширеніших прикладних патернів з GroupBox — використання CheckBox безпосередньо у Header. Коли прапорець знятий — вся група налаштувань вимикається (через IsEnabled). Це інтуїтивний UX для "необов'язкових секцій":
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<StackPanel Margin="20" Spacing="16">
<GroupBox>
<GroupBox.Header>
<CheckBox x:Name="proxyEnabledCheck"
Content="Використовувати проксі-сервер"
IsChecked="False"/>
</GroupBox.Header>
<StackPanel Margin="8" Spacing="8"
IsEnabled="{Binding ElementName=proxyEnabledCheck, Path=IsChecked}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Хост:" VerticalAlignment="Center" Foreground="Gray"/>
<TextBox Grid.Column="1" PlaceholderText="proxy.example.com" Padding="6,4"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Порт:" VerticalAlignment="Center" Foreground="Gray"/>
<TextBox Grid.Column="1" PlaceholderText="8080" MaxLength="5" Padding="6,4"/>
</Grid>
<CheckBox Content="Аутентифікація" Margin="0,4,0,0"/>
</StackPanel>
</GroupBox>
</StackPanel>
IsEnabled="{Binding ElementName=proxyEnabledCheck, Path=IsChecked}". Це той самий ElementName-binding, що ми вивчали у статті про Slider. Властивість IsEnabled на StackPanel успадковується всіма дочірніми елементами — жодного C#-коду не потрібно. Декларативне рішення у чистому XAML.Expander — HeaderedContentControl, що додає до рамки GroupBox одну ключову поведінку: вміст можна приховати та розкрити натисканням на заголовок. У згорнутому стані видно лише Header зі стрілкою-індикатором; в розгорнутому — під заголовком з'являється Content.
Цей патерн повсюдно використовується в:
Визначальна перевага Expander перед ручним приховуванням через Visibility — анімація. WPF за замовчуванням анімує розгортання та згортання вмісту — елемент плавно з'являється/зникає завдяки вбудованому стану аніматора у ControlTemplate. Жодного C#-коду для анімації не потрібно.
true — вміст видимий, false — прихований. За замовчуванням — false(закрито). Встановіть IsExpanded="True" для автоматичного розгортання при відкритті вікна.Down (вміст з'являється знизу, стрілка дивиться вниз — за замовчуванням), Up (вміст вище заголовка), Left (вміст ліворуч), Right (вміст праворуч). Для Left/Right заголовок відображається вертикально.IsExpanded → true) та згортанні (IsExpanded → false). Підписуйтесь через Expanded="Handler" у XAML або через AddHandler у коді.object, як у GroupBox. Може бути рядком, TextBlock, StackPanel з іконкою тощо. Поруч із Header завжди відображається стрілка-індикатор поточного стану.Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<StackPanel Margin="20" Spacing="4">
<TextBlock Text="Часті запитання" FontSize="16" FontWeight="Bold"
Margin="0,0,0,12"/>
<Expander Header="Як скинути пароль?" IsExpanded="True">
<TextBlock TextWrapping="Wrap" Padding="12,8"
Foreground="Gray" LineHeight="20">
Перейдіть на сторінку входу та натисніть «Забули пароль?».
Введіть вашу email-адресу — протягом 5 хвилин прийде лист
із посиланням для скидання. Посилання діє 24 години.
</TextBlock>
</Expander>
<Expander Header="Як змінити тарифний план?">
<TextBlock TextWrapping="Wrap" Padding="12,8"
Foreground="Gray" LineHeight="20">
Відкрийте розділ «Налаштування → Підписка». Оберіть потрібний план
та натисніть «Перейти». Зміна набуває чинності з наступного
платіжного циклу.
</TextBlock>
</Expander>
<Expander Header="Чи є безкоштовний тестовий період?">
<TextBlock TextWrapping="Wrap" Padding="12,8"
Foreground="Gray" LineHeight="20">
Так, ми надаємо 14 днів безкоштовного доступу до плану «Про»
без прив'язки картки. Після закінчення тріалу ваш акаунт
автоматично переходить на безкоштовний план.
</TextBlock>
</Expander>
</StackPanel>
Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<StackPanel Margin="20" Spacing="12">
<!-- Expander із XAML-заголовком (іконка + текст) -->
<Expander>
<Expander.Header>
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="⚙" FontSize="16"/>
<TextBlock Text="Розширені налаштування"
FontWeight="SemiBold"/>
<Border Background="#6366F1" CornerRadius="10" Padding="6,1">
<TextBlock Text="Beta" FontSize="10" Foreground="White"/>
</Border>
</StackPanel>
</Expander.Header>
<StackPanel Margin="0,8,0,0" Spacing="8">
<CheckBox Content="Увімкнути режим відлагодження (debug)"/>
<CheckBox Content="Показувати розширені метадані"/>
<CheckBox Content="Використовувати бета-API"/>
<TextBlock Text="⚠ Зміни набудуть чинності після перезапуску."
Foreground="#F59E0B" FontSize="11" Margin="0,4,0,0"/>
</StackPanel>
</Expander>
<!-- ExpandDirection=Up: заголовок внизу, вміст розкривається вгору -->
<Expander ExpandDirection="Up" Margin="0,20,0,0">
<Expander.Header>
<TextBlock Text="📊 Деталі статистики (розкривається вгору)"/>
</Expander.Header>
<StackPanel Margin="0,0,0,8" Spacing="4">
<TextBlock Text="• Запитів сьогодні: 1 247" Foreground="Gray"/>
<TextBlock Text="• Середній час відповіді: 82 мс" Foreground="Gray"/>
<TextBlock Text="• Помилок: 3 (0.24%)" Foreground="Gray"/>
</StackPanel>
</Expander>
</StackPanel>
Expander. Анімація розгортання у реальному WPF — більш плавна завдяки вбудованому DoubleAnimation у стандартному ControlTemplate. У Avalonia анімація може бути спрощеною або відсутньою залежно від версії.TabControl — це ItemsControl, дочірніми елементами якого є TabItem-и. Кожен TabItem є HeaderedContentControl із двома частинами:
Header — заголовок вкладки (видимий завжди, навіть коли вкладка не активна).Content — вміст вкладки (видимий лише для активної вкладки).Ця архітектура вирішує поширену задачу: відобразити кілька "сторінок" налаштувань або розділів в одному вікні без переходів між вікнами. Типові застосування — вікна налаштувань (General / Appearance / Advanced), майстри (Wizards) з кроками, деталі об'єкта з кількох аспектів (Основне / Контакти / Документи).
0 (перша вкладка). Програмне перемикання: tabControl.SelectedIndex = 2; — переходить на третю вкладку.TabItem у вигляді об'єкта. Зручніший ніж SelectedIndex у прив'язці даних.Top (за замовчуванням), Bottom, Left, Right.SelectionChangedEventArgs містить AddedItems (нова вкладка) та RemovedItems (попередня).TabItem сам по собі нащадок HeaderedContentControl, тому успадковує логіку Header/Content. Крім того:
true якщо ця вкладка активна. Зазвичай зчитується, не встановлюється — краще використовувати TabControl.SelectedIndex.false — вкладка відображається, але не клікабельна. Корисно для "кроків майстра", де доступ до наступного кроку відкривається після завершення поточного.Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<TabControl Margin="20" Height="260">
<!-- Вкладка 1: General -->
<TabItem>
<TabItem.Header>
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock Text="⚙"/>
<TextBlock Text="Загальні"/>
</StackPanel>
</TabItem.Header>
<StackPanel Margin="16" Spacing="12">
<TextBlock Text="Мова інтерфейсу:" Foreground="Gray" FontSize="12"/>
<ComboBox SelectedIndex="0" Width="200" HorizontalAlignment="Left">
<ComboBoxItem Content="🇺🇦 Українська"/>
<ComboBoxItem Content="🇬🇧 English"/>
<ComboBoxItem Content="🇩🇪 Deutsch"/>
</ComboBox>
<CheckBox Content="Запускати при старті системи"/>
<CheckBox Content="Показувати у системному треї" IsChecked="True"/>
<CheckBox Content="Перевіряти оновлення автоматично" IsChecked="True"/>
</StackPanel>
</TabItem>
<!-- Вкладка 2: Appearance -->
<TabItem>
<TabItem.Header>
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock Text="🎨"/>
<TextBlock Text="Вигляд"/>
</StackPanel>
</TabItem.Header>
<StackPanel Margin="16" Spacing="12">
<TextBlock Text="Тема:" Foreground="Gray" FontSize="12"/>
<StackPanel Spacing="6">
<RadioButton Content="🌙 Темна" IsChecked="True" GroupName="theme"/>
<RadioButton Content="☀ Світла" GroupName="theme"/>
<RadioButton Content="💻 Системна" GroupName="theme"/>
</StackPanel>
<Separator/>
<TextBlock Text="Розмір шрифту інтерфейсу:"
Foreground="Gray" FontSize="12"/>
<Slider Minimum="10" Maximum="18" Value="13"
TickFrequency="2" IsSnapToTickEnabled="True"
TickPlacement="BottomRight" Width="200"
HorizontalAlignment="Left"/>
</StackPanel>
</TabItem>
<!-- Вкладка 3: Advanced (вимкнена) -->
<TabItem IsEnabled="False">
<TabItem.Header>
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock Text="🔧"/>
<TextBlock Text="Розширені" Foreground="Gray"/>
</StackPanel>
</TabItem.Header>
<TextBlock Text="Ця вкладка недоступна" Margin="16"/>
</TabItem>
</TabControl>
Звичайний сценарій: кнопка "Далі"/"Назад" у майстрі (wizard), що керує TabControl.SelectedIndex:
private void NextStep_Click(object sender, RoutedEventArgs e)
{
int current = wizardTab.SelectedIndex;
int total = wizardTab.Items.Count;
if (current < total - 1)
{
wizardTab.SelectedIndex = current + 1;
}
// Вимикаємо "Далі" на останній вкладці
nextButton.IsEnabled = wizardTab.SelectedIndex < total - 1;
// Вмикаємо "Назад" якщо не на першій
prevButton.IsEnabled = wizardTab.SelectedIndex > 0;
}
private void PrevStep_Click(object sender, RoutedEventArgs e)
{
if (wizardTab.SelectedIndex > 0)
wizardTab.SelectedIndex--;
nextButton.IsEnabled = wizardTab.SelectedIndex < wizardTab.Items.Count - 1;
prevButton.IsEnabled = wizardTab.SelectedIndex > 0;
}
TabControl з IsEnabled="False" на вкладках — приховайте також стрілки. Перемикайте Visibility кнопок разом з IsEnabled, щоб не заплутати користувача. Для справжнього Wizard-інтерфейсу (з перевіркою даних перед переходом) перевіряйте в SelectionChanged і у разі невалідності повертайте SelectedIndex назад — з виведенням повідомлення.StatusBar — ItemsControl спеціального призначення, що традиційно розміщується у нижній частині вікна і відображає актуальну інформацію про стан застосунку: поточний режим, прогрес фонової операції, координати курсору, час останнього збереження, кількість обраних елементів.
На відміну від кнопок чи форм, StatusBar несе суто інформаційне навантаження — він не призначений для введення даних або ухвалення рішень. Це "пасивна" частина інтерфейсу, яка постійно оновлюється кодом у відповідь на зміни стану застосунку.
StatusBar може містити StatusBarItem-и (аналог ListBoxItem) — кожен є ContentControl, тобто може вміщувати будь-який UIElement. Між ними розміщують Separator для візуального поділу секцій.
StatusBar. Успадковує від ContentControl, тому Content може бути текстом, TextBlock, ProgressBar, StackPanel тощо.StatusBarItem-ами. У StatusBar автоматично відображається вертикально (на відміну від горизонтального роздільника в меню).Loading Avalonia WebAssembly...
Downloading .NET runtime (10MB)...
<Grid Margin="0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Основний вміст вікна -->
<TextBlock Grid.Row="0"
Text="Основна область застосунку"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="Gray"
FontSize="14"/>
<!-- StatusBar фіксований внизу -->
<StatusBar Grid.Row="1">
<StatusBarItem>
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock Text="✓" Foreground="#10B981" FontWeight="Bold"/>
<TextBlock Text="Готово"/>
</StackPanel>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<TextBlock Text="Рядок: 42, Стовпець: 17" Foreground="Gray"/>
</StatusBarItem>
<Separator/>
<!-- StatusBarItem з ProgressBar -->
<StatusBarItem>
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock Text="Індексування:" Foreground="Gray"/>
<ProgressBar Value="67" Minimum="0" Maximum="100"
Width="80" Height="10"/>
<TextBlock Text="67%" Foreground="Gray"/>
</StackPanel>
</StatusBarItem>
<Separator/>
<!-- Правий елемент: DockPanel або Separator+Item -->
<StatusBarItem HorizontalAlignment="Right">
<TextBlock Text="UTF-8 | CRLF | WPF 4.8"
Foreground="Gray" FontSize="11"/>
</StatusBarItem>
</StatusBar>
</Grid>
У реальних застосунках StatusBar оновлюється у відповідь на дії користувача або фонові операції:
// Оновлення статусу після збереження файлу
private async void SaveFile_Click(object sender, RoutedEventArgs e)
{
statusText.Text = "Збереження...";
saveProgressBar.Visibility = Visibility.Visible;
saveProgressBar.IsIndeterminate = true;
try
{
await Task.Run(() => SaveFileAsync()); // Симуляція збереження
statusText.Text = $"Збережено: {DateTime.Now:HH:mm:ss}";
saveProgressBar.IsIndeterminate = false;
saveProgressBar.Value = 100;
}
catch (Exception ex)
{
statusText.Text = $"Помилка: {ex.Message}";
statusText.Foreground = Brushes.OrangeRed;
}
finally
{
// Ховаємо ProgressBar через 2 секунди
await Task.Delay(2000);
saveProgressBar.Visibility = Visibility.Collapsed;
}
}
Task.Run і await вже потребують розуміння асинхронного програмування. Для навчальних цілей замінити на синхронний виклик або Thread.Sleep. Важливо: будь-яке оновлення UI з не-UI потоку потребує Dispatcher.Invoke(() => { ... }) — безпосередньо звертатися до WPF-елементів з фонового потоку заборонено.Ціль: Практика GroupBox зі звичайним і XAML-заголовком, HeaderedContentControl.
Завдання: Реалізуйте форму реєстрації користувача, організовану через GroupBox-и.
Структура:
GroupBox "👤 Персональні дані": поля Ім'я (TextBox, MaxLength=50), Прізвище, Email. Усі з Label + Target.GroupBox "🔐 Безпека": два PasswordBox (Пароль і Підтвердження), індикатор надійності пароля.GroupBox "📋 Уподобання": CheckBox "Підписатись на новини" + RadioButton-и "Частота" (GroupName="frequency"): Щодня / Щотижня / Щомісяця. RadioButton-и вимкнені (IsEnabled), якщо чекбокс не позначено — через {Binding ElementName}.GroupBox із CheckBox у заголовку "Адреса доставки" — патерн Enable Group. Вміст вимикається якщо прапорець знятий.IsDefault/IsCancel) внизу поза GroupBox-ами.Що перевірити: При зміні CheckBox у заголовку — вміст GroupBox миттєво вмикається/вимикається через ElementName-прив'язку.
Ціль: Практика TabControl, TabItem з XAML-заголовком, IsEnabled на вкладках, SelectedIndex.
Завдання: Реалізуйте повноцінне вікно "Параметри" зі структурою:
ComboBox мови, CheckBox-и "Запускати при старті/Показувати у треї/Перевіряти оновлення", TextBox "Шлях до робочої папки".RadioButton-и теми (Темна/Світла/Системна), Slider розміру шрифту (10–18, прив'язаний через ElementName до TextBlock з написом "Зразок тексту").IsEnabled="False") за замовчуванням. Додайте CheckBox "Я розумію ризики" поза TabControl — при позначенні вкладка вмикається через code-behind tabAdvanced.IsEnabled = true.Структура вікна: DockPanel → TabControl (зверху, DockPanel.Dock="Top") → панель кнопок "ОК / Скасувати / Застосувати" (знизу).
Ціль: Практика Expander у вертикальному списку, StatusBar з оновленням через код.
Завдання: Реалізуйте Dashboard-вікно моніторингу серверів.
Ліва панель (фіксована ширина, ScrollViewer):
Expander-и із серверами (Server A, B, C, D).● зелений/червоний) і назвою.StackPanel з показниками (TextBlock): CPU: 34%, RAM: 2.1/8 GB, Uptime: 12d.Expander — IsExpanded="True". Решта — закриті.Права область: текстовий RichTextBox (ReadOnly) — "лог подій" у форматі [HH:mm:ss] [Server A] Статус: OK.
StatusBar внизу:
ProgressBar (40% зайнятості, ширина 100px) + TextBlock "40%".TextBlock, оновлюється через DispatcherTimer щосекунди).Ключове відкриття цієї статті — не самі контроли, а Content Model: архітектурна концепція, що пояснює, чому WPF настільки гнучкий.
Content Model ділить усі контроли на два фундаментальні типи. ContentControl (і його нащадок HeaderedContentControl) приймає рівно один Content — але цей об'єкт може бути будь-якою складністю. Саме тому Button може містити Grid, GroupBox — StackPanel, а заголовок Expander — StackPanel з іконкою та Border. ItemsControl оперує колекцією елементів — це основа для TabControl, StatusBar, ListBox, ComboBox.
GroupBox — HeaderedContentControl для візуального та семантичного групування форм. Header може бути довільним XAML, що відкриває потужний паттерн "CheckBox у заголовку" для вмикання/вимикання цілих секцій через ElementName-прив'язку.
Expander додає до GroupBox поведінку розгортання/згортання з вбудованою анімацією. IsExpanded, ExpandDirection, XAML-заголовок — основні важелі. Незамінний для FAQ, панелей налаштувань і секцій з рідко потрібними опціями.
TabControl — ItemsControl із TabItem-ами (HeaderedContentControl). SelectedIndex керує активною вкладкою програмно. TabItem.IsEnabled="False" — зручний механізм для "кроків майстра". TabStripPlacement дозволяє розміщувати ярлики з будь-якого боку.
StatusBar — ItemsControl для відображення стану застосунку. Може містити TextBlock, ProgressBar, будь-який UIElement всередині StatusBarItem. Оновлюється виключно через код; для фонових операцій — await Task.Run() плюс Dispatcher.Invoke().
У наступній статті ми розглянемо панелі прокрутки та навігації — ScrollViewer, Frame та механізм навігації WPF. ScrollViewer — обов'язковий контейнер для будь-якого вмісту, що може виходити за межі видимої області. Frame та Page — основа для навігаційних застосунків із журналом переходів.
Контроли вибору — CheckBox, RadioButton, ComboBox, ListBox, DatePicker
Вивчаємо контроли WPF, що дозволяють обирати значення з набору варіантів — від простих прапорців і перемикачів до випадаючих списків, видимих переліків та вибору дати. Детальний розбір властивостей, подій і типових сценаріїв застосування.
UI/UX принципи десктопних застосунків
Фундаментальні принципи проєктування користувацьких інтерфейсів для десктопних застосунків. Розуміємо різницю між UI та UX, вивчаємо закони взаємодії, принципи візуальної ієрархії, типографіки, кольору та доступності. Застосовуємо теорію на практиці через WPF та Avalonia.