Desktop UI

Контроли вибору — CheckBox, RadioButton, ComboBox, ListBox, DatePicker

Вивчаємо контроли WPF, що дозволяють обирати значення з набору варіантів — від простих прапорців і перемикачів до випадаючих списків, видимих переліків та вибору дати. Детальний розбір властивостей, подій і типових сценаріїв застосування.
Нові терміни у цій статті:CheckBox, RadioButton, GroupName, ComboBox, ListBox, SelectionMode, DatePicker, Calendar, IsThreeState, IsEditable, DisplayMemberPath, SelectedItem, SelectedIndex, BlackoutDates, CalendarDateRange.

Вибір як концепція UI

Надати користувачу можливість обрати щось — одне з найфундаментальніших завдань у проєктуванні інтерфейсів. Але форматів, у яких реалізується цей вибір, чимало: чи вибирається одне значення чи декілька? Чи видимий список одразу, чи розкривається на вимогу? Чи значення відомі заздалегідь, чи вводяться вільно? Від відповіді на ці питання залежить, який саме контрол підходить для завдання.

WPF пропонує спеціалізований набір контролів вибору, кожен з яких оптимізований для конкретного UX-сценарію:

CheckBox

Незалежний прапорець для бінарного вибору (увімкнено/вимкнено). Підтримує три стани. Кілька CheckBox-ів діють незалежно один від одного.

RadioButton

Перемикач для виключного вибору одного варіанту з групи. Вибір одного автоматично скасовує всі інші у тій самій групі.

ComboBox

Список, що розкривається на вимогу. Займає мінімум місця у стандартному стані — показує лише обраний елемент. Може бути редагованим.

ListBox

Список, вся висота якого завжди видима. Підтримує вибір одного або кількох елементів одночасно.

DatePicker / Calendar

Спеціалізовані контроли для вибору дати. DatePicker — компактне поле з випадаючим календарем; Calendar — повноцінний календар завжди видимий на екрані.

CheckBox: незалежний прапорець

Архітектура та три стани

CheckBox успадковує від ToggleButton (який ми розглядали у статті про базові контроли), тому вся логіка IsChecked (nullable bool) і три стани вже знайомі. Різниця між ToggleButton і CheckBox — суто у зовнішньому вигляді та семантиці: ToggleButton виглядає як кнопка, CheckBox — як прапорець з підписом. Поведінка — ідентична.

Ключова властивість — IsChecked типу bool?:

Значення IsCheckedСтанПодія
trueПозначено (✓)Checked
falseНе позначено (□)Unchecked
nullНевизначений (─)Indeterminate

Тристановий режим (null) активується властивістю IsThreeState="True". У звичайному режимі — лише true та false.

Тристановий CheckBox має чітке UX-призначення: "батьківський" прапорець, що представляє групу дочірніх. Якщо частина дочірніх позначена — батьківський у стані null (Indeterminate). Якщо всі позначені — true. Якщо жодного — false. Цей паттерн широко використовується у деревах налаштувань (наприклад, вибір файлів для оновлення, де можна виділити групу чи окремі елементи).

Базове використання CheckBox

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Реакція на зміну стану: події та IsChecked у code-behind

Підписуватись на окремі події Checked/Unchecked/Indeterminate або читати IsChecked у будь-який момент — обидва підходи правомірні. Для простих форм зручніше зчитувати IsChecked у момент підтвердження (натискання "Зберегти"), ніж стежити за кожною зміною окремо:

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Перетворення IsChecked в bool потребує обережності. (bool)checkBox.IsCheckedкине InvalidCastException, якщо значення null (тристановий режим). Безпечні варіанти: checkBox.IsChecked == true, checkBox.IsChecked ?? false, або checkBox.IsChecked.GetValueOrDefault().

RadioButton: виключний вибір

GroupName: механізм ексклюзивності

RadioButton — ще один нащадок ToggleButton, але зі специфічною поведінкою: вибір одного RadioButton у групі автоматично скасовує усі інші у тій самій групі. Це реалізується через механізм GroupName.

Правила групування:

  1. Усі RadioButtonбез GroupName, що знаходяться в одному батьківському контейнері, утворюють одну групу автоматично.
  2. RadioButton-и з однаковим GroupName утворюють групу незалежно від їхнього розташування у дереві елементів — навіть якщо вони знаходяться у різних StackPanel-ах чи Grid-ах.
  3. Якщо один RadioButton має GroupName, а інший — ні, вони не є частиною спільної групи.

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Завжди явно вказуйте GroupName, якщо у вікні є кілька незалежних груп RadioButton-ів. Покладатись на автоматичне групування за батьківським контейнером — це джерело важкозрозумілих помилок, особливо коли в майбутньому структура розкладки змінюється.

Зчитування обраного RadioButton у code-behind

Оскільки RadioButton-и не мають єдиної властивості "обране значення" (на відміну від ComboBox.SelectedItem), у code-behind доводиться перебирати їх або перевіряти IsChecked кожного окремо:

private string GetSelectedPlan()
{
    if (rbFree.IsChecked == true)    return "Безкоштовний";
    if (rbStarter.IsChecked == true) return "Стартер";
    if (rbPro.IsChecked == true)     return "Про";
    return "Нічого не обрано";
}
У MVVM-архітектурі ця незручність усувається прив'язкою IsChecked кожного RadioButton до окремого булевого поля ViewModel. Але це тема Блоку 6–7. Поки — code-behind цілком достатній.

ComboBox: список, що розкривається

Концептуальна модель ComboBox

ComboBox — це контрол, що у звичайному стані займає місце одного рядка і показує лише поточний обраний елемент. При натисканні — розкривається drop-down список з усіма варіантами. Після вибору — знову згортається. Ця модель ідеальна для вибору з довгого списку варіантів, коли показувати всі одразу займало б забагато місця.

Під капотом ComboBox успадковує від Selector → ItemsControl. Це важлива деталь: він, як і ListBox, побудований на концепції Items та ItemsSource. У цій статті ми заповнюємо Items статично (у XAML або у code-behind) — прив'язку до колекцій через ItemsSource розглянемо у Блоці 6.

Ключові властивості ComboBox

SelectedItem
object
Обраний елемент. Для статичних ComboBoxItem-ів повертає сам ComboBoxItem. При прив'язці колекції — повертає об'єкт з колекції. За замовчуванням — null (нічого не обрано).
SelectedIndex
int
Індекс обраного елемента (0-based). -1 — нічого не обрано. Зручний для встановлення вибору за замовчуванням: SelectedIndex="0" обере перший елемент.
SelectedValue
object
Значення, визначене через SelectedValuePath. Якщо SelectedValuePath="Id", то SelectedValue повертає поле Id обраного об'єкта — без необхідності кастувати SelectedItem.
IsEditable
bool
Якщо true — у закритому стані замість статичного тексту відображається TextBox для ручного введення. Корисно для "combobox з автодоповненням". За замовчуванням — false.
DisplayMemberPath
string
Імʼя властивості обʼєкта (з ItemsSource), яку потрібно відображати у списку. Без цієї властивості — відображається результат ToString(). Докладніше — у Блоці 6.
Text
string
У режимі IsEditable="True" — містить поточний текст у редагованому полі (введений вручну або відповідний обраному елементу).

Статичне наповнення через XAML

Найпростіший спосіб заповнити ComboBox — додати ComboBoxItem-и безпосередньо у XAML:

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Статичне наповнення через code-behind

Якщо список формується динамічно (наприклад, за результатами запиту, але ще без Binding), можна додати рядки безпосередньо у C#:

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    var countries = new[] { "Україна", "Польща", "Німеччина", "Франція", "США" };

    foreach (var country in countries)
    {
        countryCombo.Items.Add(country);
    }

    countryCombo.SelectedIndex = 0; // Обираємо перший за замовчуванням
}

private void CountryCombo_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    // SelectedItem для рядків — це сам рядок
    string selected = countryCombo.SelectedItem?.ToString() ?? "нічого";
    statusLabel.Content = $"Обрано: {selected}";
}
Якщо у ComboBox.Items лежать прості рядки — SelectedItem повертає string, кастування не потрібне. Якщо — ComboBoxItem-и — тоді (comboBox.SelectedItem as ComboBoxItem)?.Content?.ToString(). При прив'язці до колекції об'єктів — SelectedItem повертає сам об'єкт: (comboBox.SelectedItem as MyClass)?.Name. В усіх цих випадках SelectedValuePath суттєво спрощує роботу.

ListBox: видимий список із вибором

ListBox проти ComboBox

Якщо ComboBox — це "акордеон" (вибір прихований до натискання), то ListBox — це "таблиця" (всі рядки видимі одразу). Обирайте ListBox, коли:

  • Кількість елементів невелика (3–15) і показ усіх одночасно не переповнює UI.
  • Потрібен множинний вибір (кілька елементів одразу) — ComboBox це не підтримує.
  • Важлива порівнянність варіантів — коли видимість усіх одночасно допомагає ухвалити рішення.

Ключова властивість, що відрізняє ListBox від ComboBoxSelectionMode:

ЗначенняПоведінка
SingleВибір лише одного елемента (за замовчуванням)
MultipleКлік на елемент перемикає його виділення незалежно (без Ctrl)
ExtendedВиділення з Shift (діапазон) та Ctrl (окремі елементи), як у провіднику

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Зчитування вибраних елементів

Для Single-режиму — SelectedItem та SelectedIndex, як у ComboBox. Для Multiple/Extended — колекція SelectedItems:

// Single
private void SingleListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    var item = singleListBox.SelectedItem as ListBoxItem;
    statusText.Text = $"Обрано: {item?.Content}";
}

// Multiple / Extended
private void MultiListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    var selected = multiListBox.SelectedItems
        .Cast<ListBoxItem>()
        .Select(i => i.Content?.ToString())
        .ToList();

    statusText.Text = selected.Count > 0
        ? $"Обрано: {string.Join(", ", selected)}"
        : "Нічого не обрано";
}
SelectedItems повертає IList, не IList<T>. Для роботи з ним потрібне приведення через Cast<T>() або перебір у циклі. При прив'язці до типізованих колекцій через ItemsSourceCast<MyClass>() повертає об'єкти потрібного типу без обхідних рукоділь.

DatePicker: вибір дати

Навіщо спеціалізований контрол для дати

Здається, введення дати через звичайний TextBox — очевидне рішення. Але це рішення, що плодить проблеми: різні формати дат у різних культурах (dd.MM.yyyy в Україні, MM/dd/yyyy в США), помилки введення, неможливість одразу побачити день тижня, необхідність самостійно валідувати рядок. DatePicker вирішує всі ці проблеми — він дає користувачу зручний календар і повертає розробнику готовий DateTime?.

DatePicker — компактний контрол: у нормальному стані показує поточну обрану дату (або порожнє поле), при натисканні на іконку — розкривається мінікалендар. Після вибору — знову згортається. Це аналог ComboBox, але для дат.

SelectedDate
DateTime?
Обрана дата. null — нічого не обрано. Найчастіше прив'язується до DateTime?-властивості ViewModel. Встановлення через XAML: SelectedDate="{x:Static sys:DateTime.Today}" (потребує xmlns:sys="clr-namespace:System;assembly=mscorlib").
DisplayDateStart
DateTime?
Найменша дата, що відображається у календарі. Дати раніше цього значення приховані (не просто заблоковані — повністю відсутні). Корисно для "вибору майбутньої дати": DisplayDateStart="{x:Static sys:DateTime.Today}".
DisplayDateEnd
DateTime?
Найбільша дата у календарі. Дати пізніше — приховані.
BlackoutDates
CalendarBlackoutDatesCollection
Колекція заблокованих дат або діапазонів. Відображаються у календарі, але вибрати їх неможливо. Ідеально для "вихідних" або "вже зайнятих" дат у формі бронювання.
FirstDayOfWeek
DayOfWeek
Перший день тижня у відображенні календаря. За замовчуванням — системне значення (у деяких локалях це Неділя, а не Понеділок, як прийнято в Україні).
SelectedDateFormat
DatePickerFormat
Формат відображення обраної дати у текстовому полі: Short (стислий, відповідно до культури) або Long (розгорнутий). За замовчуванням — Short.

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Блокування конкретних дат через BlackoutDates

Для форм бронювання типовий сценарій — заблокувати вже зайняті дати або вихідні. BlackoutDates приймає CalendarDateRange:

private void SetupBookingCalendar()
{
    var picker = bookingDatePicker;

    // Блокуємо всі минулі дати разом з сьогодні
    picker.BlackoutDates.AddDatesInPast();

    // Блокуємо конкретний діапазон (наприклад, технічна перерва)
    picker.BlackoutDates.Add(
        new CalendarDateRange(
            new DateTime(2026, 4, 14),
            new DateTime(2026, 4, 18)
        )
    );

    // Блокуємо всі вихідні (суботи та неділі)
    DateTime current = DateTime.Today;
    DateTime endDate = current.AddYears(1);
    while (current <= endDate)
    {
        if (current.DayOfWeek == DayOfWeek.Saturday
            || current.DayOfWeek == DayOfWeek.Sunday)
        {
            picker.BlackoutDates.Add(new CalendarDateRange(current));
        }
        current = current.AddDays(1);
    }
}
Блокування вихідних через цикл — поширений підхід, але витратний для великих діапазонів. В альтернативних підходах використовують DisplayDateChanged-подію Calendar і перефарбовують осередки через кастомний CalendarDayButtonStyle (це вже тема стилізації, Блок 8). Для навчальних цілей — цикл цілком прийнятний.

Calendar: повноцінний вбудований календар

Calendar vs DatePicker

Calendar — це той самий "мінікалендар", що розкривається у DatePicker, але виведений як самостійний завжди видимий контрол. Використовуйте його, коли:

  • Вибір дати — центральна дія екрану, а не допоміжна.
  • Потрібно показати кілька дат або діапазон.
  • Потрібен SelectionMode="MultipleRange"DatePicker не підтримує множинний вибір.
SelectedDate
DateTime?
Обрана дата (у режимах SingleDate та SingleRange).
SelectedDates
SelectedDatesCollection
Колекція обраних дат (у режимах Multiple та MultipleRange). Доступна лише для читання, але можна викликати Add() для програматичного вибору.
SelectionMode
CalendarSelectionMode
Режим вибору: SingleDate (один день), SingleRange (суцільний діапазон), MultipleRange (кілька не суміжних діапазонів), None (вибір заблоковано — Calendar тільки для відображення).

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Превью використовує Avalonia Fluent Theme. Зовнішній вигляд Calendar та DatePicker у реальному WPF буде помітно іншим (класична Aero-тема із сірими кнопками). Поведінка (SelectionMode, BlackoutDates, SelectedDate) — ідентична. Для стилізованого вигляду у WPF використовують бібліотеки тем (MahApps.Metro, ModernWPF).

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


Підсумок

Що ми вивчили у цій статті

Ця стаття охопила шість контролів вибору — від найпростіших прапорців до повноцінного датапікера. Узагальнимо ключові висновки.

CheckBox — незалежний прапорець із трьома станами (true/false/null). Тристановий режим (IsThreeState="True") використовується для "батьківських" прапорців, що представляють групу. При зчитуванні IsChecked завжди використовуйте == true або ?? false — ніколи пряме приведення.

RadioButton — виключний вибір одного варіанту з групи. Групування через GroupName — єдиний надійний спосіб, якщо у вікні кілька незалежних груп. Для зчитування обраного у code-behind — перевірка IsChecked кожного окремо; в MVVM — прив'язка до bool-властивостей ViewModel.

ComboBox — drop-down список з підтримкою редагованого введення (IsEditable). Статичне наповнення — через ComboBoxItem-и у XAML або Items.Add() у code-behind. SelectedItem/SelectedIndex/SelectedValue — три способи зчитати обраний елемент. ItemsSource для прив'язки до колекцій — у Блоці 6.

ListBox — видимий список з підтримкою множинного вибору (SelectionMode). Режим Extended відтворює поведінку провідника Windows (Shift + Ctrl). SelectedItems для множинного вибору повертає нетипізований IList — потрібен Cast<T>().

DatePicker — компактний вибір дати з калькулятором через BlackoutDates. DisplayDateStart/DisplayDateEnd ховають недоступні дати; BlackoutDates — блокують конкретні. AddDatesInPast() — зручний метод для "тільки майбутнє".

Calendar — повноцінний завжди видимий календар. SelectionMode="MultipleRange" дозволяє обирати кілька несуміжних діапазонів — можливість, недоступна у DatePicker.

Що далі

У наступній статті ми розглянемо контроли-контейнери вмістуGroupBox, Expander, ScrollViewer, TabControl, Frame. Ці контроли не відображають дані самі по собі, а організовують інші контроли у структуровані групи, розділи та вкладки — будівельні блоки складних багатосторінкових форм.