Desktop UI

Текстові контроли — TextBlock, TextBox, RichTextBox

Детально розглядаємо контроли для відображення та введення тексту у WPF — від легковісного TextBlock до повноцінного RichTextBox із FlowDocument. Розуміємо різницю між відображенням і введенням, форматованим і неформатованим текстом, захищеним введенням через PasswordBox.
Нові терміни у цій статті:TextBlock, Label, TextBox, PasswordBox, RichTextBox, FlowDocument, Run, Inline, TextWrapping, TextTrimming, AcceptsReturn, SecureString, PasswordChar, Selection, Paragraph, AccessKey, Adorner.

Текст у WPF: відображення проти введення

Текст — це найфундаментальніший елемент будь-якого застосунку. Майже кожен екран містить підписи, поля введення, повідомлення, форматований вміст. Але якщо у WinForms для всіх цих завдань часто використовувався один і той самий Label або TextBox, то WPF пропонує спеціалізований набір контролів, кожен з яких є оптимальним для конкретного сценарію.

Розуміння того, який контрол для якої задачі призначений, є однією з основ ефективного проєктування WPF-інтерфейсів. Помилковий вибір тут — не просто естетична проблема: використання TextBox там, де достатньо TextBlock, додає зайве навантаження на систему; використання Label замість TextBlock без потреби — це надлишкова складність. Кожен контрол несе у собі певний набір функцій та відповідних витрат.

У цій статті ми розглянемо п'ять ключових текстових контролів WPF:

TextBlock

Легковісний контрол виключно для відображення тексту. Не може отримувати фокус, не підтримує введення. Підтримує багатий Inline-форматований текст (Bold, Italic, Hyperlink). Найбільш використовуваний текстовий контрол.

Label

Обгортка над ContentControl з підтримкою AccessKey (підкреслена буква-скорочення) та прив'язки до контролу через Target. Для простого тексту — надлишковий, але незамінний для доступних форм.

TextBox

Повноцінне поле введення тексту (одно- або багаторядкове). Підтримує Undo/Redo, виділення, буфер обміну. Основний елемент введення у WPF.

PasswordBox

Спеціалізований контрол для введення паролів. Маскує символи, зберігає значення у SecureString (а не у звичайному string) — фундаментальне рішення безпеки.

RichTextBox

Повноцінний редактор форматованого тексту на основі FlowDocument. Підтримує параграфи, жирний/курсивний текст, зображення всередині тексту. Специфічний для WPF.

TextBlock vs Label: коли що використовувати

Чому це важливо

Початківці в WPF нерідко ставлять питання: "Я бачу TextBlock і Label — вони виглядають однаково. Чому існують обидва?" Це логічне запитання, і відповідь на нього розкриває кілька важливих принципів платформи.

Різниця між TextBlock та Label — не стилістична, а архітектурна:

АспектTextBlockLabel
Базовий класFrameworkElementContentControl
Може містити вмістТільки текст (Inlines)Будь-який UIElement
Підтримка фокусу❌ Ні✅ Так (але не отримує ввід)
AccessKey (_)❌ Ні✅ Так
Властивість Target❌ Ні✅ Так
Вага (memory/CPU)ЛегкийВажчий (ContentControl overhead)
Типовий use caseБудь-який теxт для відображенняПідписи у формах із клавіатурним скороченням

TextBlock: легковісний відображувач тексту

TextBlock — це не контрол у повному розумінні слова (він не успадковує від Control), а FrameworkElement. Це означає, що він не має VisualState-машини, не може отримати фокус клавіатури, не генерує події введення. Він просто відображає текст максимально ефективно.

Саме завдяки цьому TextBlock є найпоширенішим текстовим елементом у будь-якому WPF-застосунку — він з'являється скрізь: у DataTemplate-ах списків, у кнопках (всередині Content), у ToolTip-ах, у заголовках. Ніколи не використовуйте Label там, де потрібно просто відобразити текст — це надлишковий вибір.

Label: підпис для доступних форм

Label успадковує від ContentControl, що надає йому дві суттєві здатності, яких немає у TextBlock:

AccessKey — підкреслена буква у тексті підпису, яка дозволяє активувати пов'язаний контрол натисканням Alt + ця літера. Щоб визначити AccessKey, поставте символ підкреслення _ перед відповідною буквою у рядку Content:

<!-- "_І" означає Alt+І активує пов'язаний TextBox -->
<Label Content="_Ім'я:" Target="{Binding ElementName=nameTextBox}"/>
<TextBox x:Name="nameTextBox"/>

Target — властивість типу UIElement, що вказує, який контрол отримає фокус при спрацюванні AccessKey. Це ключовий механізм доступності (accessibility) для користувачів, що керують застосунком лише з клавіатури.

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

У реальних застосунках, орієнтованих на доступність (accessibility), використовуйте Label + Target для кожного поля введення. Це дозволяє користувачам зі скрин-рідерами та клавіатурній навігації правильно ідентифікувати поля. У простих формах без вимог до accessibility — достатньо TextBlock.

TextBlock: детальний розгляд

Базові властивості відображення тексту

Незважаючи на зовнішню простоту, TextBlock має набір важливих властивостей, без знання яких важко побудувати правильний текстовий інтерфейс.

Найкритичніші — TextWrapping та TextTrimming. Вони відповідають за те, що відбувається з текстом, коли він не вміщується у відведений простір.

TextWrapping визначає, чи переноситься текст на новий рядок:

NoWrap
TextWrapping
Текст ніколи не переноситься — якщо не вміщується, обрізається або виходить за межі. Це значення за замовчуванням. Підходить для однорядкових заголовків і підписів фіксованого розміру.
Wrap
TextWrapping
Текст переноситься на новий рядок при досягненні правої межі контейнера. TextBlock автоматично розраховує висоту, щоб відобразити весь вміст. Найпоширеніший у листах опису, підказках, блоках із довільним текстом.
WrapWithOverflow
TextWrapping
Текст переноситься, але окремі довгі слова, що не вміщуються в один рядок, не розбиваються — вони "виходять" за межу. На відміну від Wrap, де довге слово просто обрізається; тут воно відображається цілком, навіть якщо простір закінчився.

TextTrimming визначає, що робити з текстом, що не вміщується (актуально лише при NoWrap):

ЗначенняПоведінка
NoneТекст просто обрізається (без символів)
CharacterEllipsisОбрізається на межі довільного символу, додається ...
WordEllipsisОбрізається на межі цілого слова, додається ...

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Inline-елементи: форматований текст без RichTextBox

Одна з прихованих суперсил TextBlock — підтримка Inline-елементів: фрагментів тексту з різним форматуванням, що розміщуються всередині одного TextBlock як дочірні елементи колекції Inlines. Це дозволяє реалізувати форматований текст (жирний, курсивний, гіперпосилання, змішані стилі) без RichTextBox.

Найважливіші класи:

Run
Inline
Основний будівельний блок — фрагмент тексту з однорідним форматуванням. Властивості FontWeight, FontStyle, Foreground, FontSize застосовуються лише до цього фрагменту.
Bold / Italic / Underline
Inline (Span)
Span-контейнери зі зручними "семантичними" іменами. <Bold>текст</Bold> рівнозначно <Run FontWeight="Bold">текст</Run>. Можна вкладати: <Bold><Italic>жирний курсив</Italic></Bold>.
Hyperlink
Inline
Клікабельне посилання всередині текстового потоку. Має властивість NavigateUri та подію RequestNavigate. Для відкриття у браузері потрібен обробник RequestNavigate з викликом Process.Start.
LineBreak
Inline
Примусовий перенос рядка всередині TextBlock. Аналог тегу <br/> у HTML.

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Inline-елементи всередині TextBlock — чисто декларативне рішення. Воно підходить для статичного форматованого тексту (підписи, повідомлення, довідка). Якщо форматування потрібно змінювати динамічно (редактор, де користувач вибирає жирний через кнопку) — використовуйте RichTextBox.

TextBox: основний елемент введення

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

TextBox — найуніверсальніший контрол введення у WPF. Він успадковує від TextBoxBase, який, у свою чергу, успадковує від Control. Це означає, що TextBox — це повноцінний контрол зі всіма наслідками: він може отримувати фокус, реагує на клавіатуру та мишу, підтримує теми і стилі, має VisualState-машину (Normal, MouseOver, Focused, Disabled).

Ключові можливості, які відрізняють WPF-TextBox від WinForms-аналога, включають:

  • Вбудовану підтримку Undo/Redo (необмежених кроків, через Ctrl + Z / Ctrl + Y).
  • Виділення тексту через SelectionStart, SelectionLength, SelectedText.
  • Можливість прив'язки даних до властивості Text у двосторонньому режимі (Two-Way Binding) — основа для форм у MVVM.
  • Spell-checking (перевірка орфографії) через SpellCheck.IsEnabled="True".

Базові властивості TextBox

Text
string
Основна властивість — поточне значення тексту у полі. Підтримує прив'язку даних (Two-Way за замовчуванням у WPF). Зміна через код: myTextBox.Text = "Текст";.
AcceptsReturn
bool
Якщо true — клавіша Enter додає новий рядок (перетворює TextBox на багаторядковий). Якщо false (за замовчуванням) — Enter передає фокус до кнопки з IsDefault="True".
AcceptsTab
bool
Якщо true — клавіша Tab вставляє символ табуляції. Якщо false (за замовчуванням) — Tab переміщує фокус на наступний контрол.
MaxLength
int
Максимально допустима кількість символів. 0 (за замовчуванням) — без обмежень. Обмеження відбувається на рівні введення — користувач просто не зможе надрукувати більше.
IsReadOnly
bool
Якщо true — текст не можна редагувати, але можна виділяти і копіювати. Відрізняється від IsEnabled="False": readonly поле залишається візуально активним і дозволяє читання.
TextWrapping
TextWrapping
Те саме, що у TextBlock. Для багаторядкового TextBox встановлюйте TextWrapping="Wrap", щоб рядки автоматично переносились.
VerticalScrollBarVisibility
ScrollBarVisibility
Відображення вертикальної смуги прокрутки: Disabled (за замовчуванням), Auto (з'являється при потребі), Visible (завжди видима), Hidden (прихована, але прокрутка працює).

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

У превью використовується PlaceholderText — властивість, яка є у Avalonia, але відсутня у стандартному WPF. У реальному WPF placeholder-текст реалізовується або через стиль (Style + Trigger на Text == ""), або через Adorner-шар у code-behind. Ми розглянемо code-behind підхід нижче.

Подія TextChanged

Щоразу, коли вміст TextBox змінюється, спрацьовує подія TextChanged. Її обробник отримує аргумент TextChangedEventArgs, що містить колекцію Changes — список конкретних змін (вставлення, видалення) з позиціями. Для більшості задач достатньо просто читати myTextBox.Text всередині обробника:

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Placeholder text через code-behind (без стилів)

У стандартному WPF немає вбудованої властивості PlaceholderText. Один із найпростіших способів реалізувати підказку-заповнювач без стилів — підписатись на події GotFocus та LostFocus:

private const string PlaceholderText = "Введіть ваше ім'я...";

private void SearchBox_Loaded(object sender, RoutedEventArgs e)
{
    searchBox.Text = PlaceholderText;
    searchBox.Foreground = Brushes.Gray;
}

private void SearchBox_GotFocus(object sender, RoutedEventArgs e)
{
    if (searchBox.Text == PlaceholderText)
    {
        searchBox.Text = "";
        searchBox.Foreground = Brushes.White; // або SystemColors.ControlTextBrush
    }
}

private void SearchBox_LostFocus(object sender, RoutedEventArgs e)
{
    if (string.IsNullOrWhiteSpace(searchBox.Text))
    {
        searchBox.Text = PlaceholderText;
        searchBox.Foreground = Brushes.Gray;
    }
}
Цей підхід — простий, але він має суттєвий недолік: якщо TextBox прив'язаний до моделі через Binding, значення placeholder-тексту потрапить у модель при втраті фокусу без введення. Для правильного placeholder у реальних застосунках використовуйте підхід через Style + Trigger (Блок 8) або Adorner-шар. Поки — code-behind достатній для навчання.

PasswordBox: захищене введення

Чому у PasswordBox немає властивості Text

Це питання задає кожен, хто вперше стикається з PasswordBox. Здавалося б — TextBox має Text, PasswordBox — ні. Чому? Відповідь лежить у площині безпеки.

Звичайний string у .NET — незмінний (immutable) об'єкт, що зберігається у купі (heap). Управління пам'яттю у .NET здійснює збирач сміття (GC), і GC не гарантує, коли саме пам'ять, де зберігався рядок із паролем, буде очищена або перезаписана. Це означає, що пароль може "жити" у пам'яті ще довго після того, як ви закінчили з ним роботу — і теоретично бути прочитаним при дампі пам'яті або через уразливість.

PasswordBox зберігає пароль у SecureString — спеціальному класі, що:

  1. Шифрує вміст прямо у пам'яті (DPAPI-шифрування).
  2. Дозволяє явно очистити вміст після використання (метод Dispose()).
  3. Перешкоджає читанню через рефлексію.
Password
string
Властивість для зчитування пароля у вигляді звичайного string. Зручна, але менш безпечна — використовуйте лише для простих сценаріїв без вимог до безпеки (не для production-авторизації).
SecurePassword
SecureString
Пароль у вигляді SecureString. Повинен передаватись у функції, що приймають SecureString (наприклад, NetworkCredential), і очищатись після використання через Dispose().
PasswordChar
char
Символ-маскування замість реальних символів. За замовчуванням — (U+25CF). Можна змінити, наприклад, на * для класичного вигляду.
MaxLength
int
Максимально допустима кількість символів парoлю. 0 — без обмежень.

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Ніколи не читайте PasswordBox.Password у production-коді для реальної авторизації. Це повертає звичайний string, що залишається у пам'яті. Для авторизації передавайте SecurePassword у відповідні API (NetworkCredential, функції Windows DPAPI або зашифровані канали зв'язку). Якщо ваш застосунок сам хешує пароль для бази — використовуйте маршал-копіювання SecureString у char[], хешуйте і одразу очищайте масив.

RichTextBox та FlowDocument

Коли потрібен RichTextBox

RichTextBox — найважчий із текстових контролів. Він надає повноцінний редактор форматованого тексту на основі FlowDocument — WPF-специфічного формату представлення документів. Якщо TextBox із AcceptsReturn="True" — це "блокнот", то RichTextBox — це "Word".

Сценарії, де RichTextBox є правильним вибором:

  • Редактор нотаток із форматуванням.
  • Форма зворотного зв'язку з підтримкою форматованого тексту.
  • Вбудований HTML/RTF-редактор.

Сценарії, де RichTextBox надмірний:

  • Простий коментар або коротке повідомлення — достатньо TextBox.
  • Відображення форматованого тексту (без редагування) — достатньо TextBlock з Inline-елементами.
RichTextBox разом із FlowDocument є специфічними для WPF і не мають прямого аналога в Avalonia. Якщо ваш проєкт вимагає кросплатформності — розгляньте сторонні альтернативи (наприклад, AvalonEdit для Avalonia) або обмежтесь стандартним TextBox. Превью нижче виконується в Avalonia і може не підтримувати всі елементи FlowDocument.

FlowDocument: модель вмісту RichTextBox

FlowDocument — це ієрархічна модель документа, яка описує текст як послідовність блоків (Block). Найпоширеніший блок — Paragraph. Всередині Paragraph розміщуються Inline-елементи (Run, Bold, Italic, Hyperlink). У XAML вміст RichTextBox описується так:

<RichTextBox>
  <FlowDocument>
    <Paragraph>
      <Run>Перший параграф зі </Run>
      <Bold>жирним</Bold>
      <Run> та </Run>
      <Italic>курсивним</Italic>
      <Run> текстом.</Run>
    </Paragraph>
    <Paragraph>
      Другий параграф — щоразу новий відступ.
    </Paragraph>
  </FlowDocument>
</RichTextBox>

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Зміна форматування виділеного тексту через Selection

Щоб додати до RichTextBox кнопки форматування (жирний, курсив, розмір шрифту), використовується метод Selection.ApplyPropertyValue(). Selection — це об'єкт типу TextSelection, що представляє поточне виділення у RichTextBox:

// Зробити виділений текст жирним
private void BoldButton_Click(object sender, RoutedEventArgs e)
{
    // Читаємо поточний стан виділення
    object currentWeight = richEditor.Selection.GetPropertyValue(
        TextElement.FontWeightProperty
    );

    // Перемикаємо між Bold і Normal
    FontWeight newWeight = (currentWeight is FontWeight fw && fw == FontWeights.Bold)
        ? FontWeights.Normal
        : FontWeights.Bold;

    richEditor.Selection.ApplyPropertyValue(
        TextElement.FontWeightProperty,
        newWeight
    );

    richEditor.Focus(); // Повертаємо фокус до редактора
}

// Змінити розмір шрифту виділення
private void FontSizeCombo_SelectionChanged(object sender, ...)
{
    if (fontSizeCombo.SelectedItem is string sizeStr
        && double.TryParse(sizeStr, out double size))
    {
        richEditor.Selection.ApplyPropertyValue(
            TextElement.FontSizeProperty,
            size
        );
    }
}

Метод ApplyPropertyValue приймає будь-яку DependencyProperty з простору TextElement, Paragraph, Inline — тобто будь-яке форматування, яке підтримує модель документа. Це робить RichTextBox надзвичайно гнучким, але й складнішим у роботі порівняно зі звичайними контролами.


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


Підсумок

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

У цій статті ми розглянули п'ять текстових контролів WPF і розібрались, коли який з них є правильним вибором.

TextBlock vs Label — не просто стилістична різниця: TextBlock є легковісним FrameworkElement без фокусу і підходить для будь-якого відображення тексту; Label — повноцінний ContentControl із підтримкою AccessKey і Target, незамінний для доступних форм. Використовуйте Label лише там, де потрібні ці можливості.

TextBlock з Inline-елементами — потужний спосіб отримати форматований текст без RichTextBox. Bold, Italic, Hyperlink, Run, LineBreak — цілий набір Inline-класів для оформлення статичного тексту. TextWrapping і TextTrimming контролюють поведінку при переповненні.

TextBox — основний контрол введення із широким набором опцій: AcceptsReturn (багаторядковий), MaxLength, IsReadOnly, TextChanged. Placeholder-текст у стандартному WPF реалізується через GotFocus/LostFocus або Style + Trigger.

PasswordBox зберігає пароль у SecureString — принципове архітектурне рішення безпеки. Властивість Password зручна, але менш безпечна; для production-коду — SecurePassword. PasswordChar дозволяє змінити символ маскування.

RichTextBox + FlowDocument — повноцінний редактор форматованого тексту. Вміст описується через Paragraph + Inline-елементи. Форматування виділення змінюється через Selection.ApplyPropertyValue(). Специфічний для WPF — не має прямого аналога в Avalonia.

Що далі

У наступній статті ми розглянемо контроли виборуCheckBox, RadioButton, ComboBox, ListBox. Ці контроли будуються поверх вже вивчених (ToggleButton, ItemsControl) і вводять нову концепцію — вибір з набору варіантів, що є фундаментом для зв'язування з колекціями даних.