GUI и визуальные компоненты

RichTextBox в C#: разбор и примеры

Обновлено: · 5 мин чтения

RichTextBox - элемент Windows Forms для ввода и показа форматированного текста: шрифты, цвета, выравнивание, списки и картинки в формате RTF. Он нужен там, где обычного TextBox мало: в простых редакторах, консолях логов, окнах справки. Ниже свойства, форматирование, загрузка и сохранение файлов, поиск с подсветкой и частые проблемы.

RichTextBox в C#: разбор и примеры
Содержание
  1. Чем RichTextBox отличается от TextBox
  2. Добавление на форму
  3. Основные свойства
  4. Форматирование выделенного текста
    1. Переключение жирного и курсива
  5. Добавление цветного текста в конец
  6. Загрузка и сохранение файлов
  7. Поиск и подсветка текста
  8. Работа с RTF
  9. События
  10. Вставка только простого текста
  11. Типичные проблемы
  12. Альтернативы
  13. Частые вопросы

Чем RichTextBox отличается от TextBox

В обычном TextBox весь текст оформлен одним шрифтом и одним цветом. RichTextBox позволяет задавать оформление отдельным фрагментам, вставлять изображения, хранить документ в RTF и открывать файлы из WordPad или Word в простом виде. Платой за это становится больший расход ресурсов и более сложный API.

ВозможностьTextBoxRichTextBox
Разные шрифты и цвета в текстеНетДа
Формат RTFНетДа, свойство Rtf
Маркированные списки, отступыНетДа
Вставка изображенийНетЧерез буфер обмена и RTF
Автоматические ссылкиНетСвойство DetectUrls
Скорость на больших объёмахВышеНиже

Добавление на форму

Элемент можно перетащить из панели элементов в конструкторе Visual Studio или создать программно. Во втором случае важно не забыть добавить его в коллекцию Controls формы, иначе на экране ничего не появится.

var rtb = new RichTextBox
{
    Location = new Point(10, 10),
    Size = new Size(500, 300),
    Font = new Font("Segoe UI", 11),
    ScrollBars = RichTextBoxScrollBars.Vertical,
    WordWrap = true,
    Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right
};
Controls.Add(rtb);

Свойство Anchor с четырьмя значениями заставит поле растягиваться вместе с окном. Для окна с одним редактором проще задать Dock = DockStyle.Fill. Другие элементы управления на форме описаны в разделе блога про компоненты Windows Forms.

Основные свойства

СвойствоНазначение
TextПростой текст без оформления
RtfСодержимое вместе с форматированием в виде RTF-строки
SelectedText, SelectionStart, SelectionLengthВыделенный фрагмент и его позиция
SelectionFont, SelectionColor, SelectionBackColorОформление выделенного текста
SelectionAlignmentВыравнивание абзаца: слева, по центру, справа
SelectionBullet, SelectionIndentМаркер списка и отступ
ReadOnlyЗапрещает редактирование
WordWrapПеренос по словам
DetectUrlsПодсвечивать ссылки
ZoomFactorМасштаб от 0.015625 до 64
CanUndo, CanRedoДоступность отмены и повтора

Форматирование выделенного текста

Оформление применяют к текущему выделению. Сначала выделяют диапазон методом Select, затем задают свойства, начинающиеся на Selection.

rtb.Text = "Заголовок документа\nОбычный абзац с важным словом.";

rtb.Select(0, 19);
rtb.SelectionFont = new Font("Segoe UI", 16, FontStyle.Bold);
rtb.SelectionColor = Color.DarkBlue;
rtb.SelectionAlignment = HorizontalAlignment.Center;

int pos = rtb.Text.IndexOf("важным", StringComparison.Ordinal);
rtb.Select(pos, "важным".Length);
rtb.SelectionBackColor = Color.Yellow;

rtb.Select(rtb.TextLength, 0);   // снять выделение, курсор в конец

Переключение жирного и курсива

Типичная задача редактора - кнопка "Ж", которая включает или выключает жирность для выделения. Ошибка в том, что SelectionFont равно null, когда в выделении смешаны разные шрифты. Эту ситуацию нужно обработать.

private void ToggleStyle(RichTextBox box, FontStyle style)
{
    var current = box.SelectionFont ?? box.Font;
    var newStyle = current.Style ^ style;   // XOR переключает бит стиля
    box.SelectionFont = new Font(current, newStyle);
}

// на кнопках
private void btnBold_Click(object sender, EventArgs e) => ToggleStyle(rtb, FontStyle.Bold);
private void btnItalic_Click(object sender, EventArgs e) => ToggleStyle(rtb, FontStyle.Italic);

Так как объект Font хранит ресурсы GDI, при частом создании шрифтов нужен контроль утечек. В примере предыдущий шрифт принадлежит элементу, поэтому его не освобождают вручную.

Добавление цветного текста в конец

Для логов и консолей важно дописывать строки с разным цветом. Метод AppendText сам по себе цвет не меняет, поэтому перед вставкой выделяют конец документа и задают SelectionColor.

public static void AppendColored(RichTextBox box, string text, Color color)
{
    box.SelectionStart = box.TextLength;
    box.SelectionLength = 0;
    box.SelectionColor = color;
    box.AppendText(text);
    box.SelectionColor = box.ForeColor;   // вернуть цвет по умолчанию
    box.ScrollToCaret();
}

AppendColored(rtb, "Ошибка: файл не найден\n", Color.Firebrick);
AppendColored(rtb, "Готово\n", Color.ForestGreen);

Если логов очень много, элемент начнёт тормозить. Ограничьте размер, удаляя старые строки, или выводите сообщения пакетами раз в несколько десятков миллисекунд, а не по одной.

Загрузка и сохранение файлов

RichTextBox умеет читать и писать файлы без ручной работы со строками. Метод LoadFile открывает RTF или обычный текст, SaveFile записывает документ в нужном формате, который задаётся значением RichTextBoxStreamType.

private void OpenFile()
{
    using var dlg = new OpenFileDialog
    {
        Filter = "RTF (*.rtf)|*.rtf|Текст (*.txt)|*.txt"
    };
    if (dlg.ShowDialog() != DialogResult.OK) return;

    var type = Path.GetExtension(dlg.FileName).Equals(".rtf", StringComparison.OrdinalIgnoreCase)
        ? RichTextBoxStreamType.RichText
        : RichTextBoxStreamType.PlainText;

    rtb.LoadFile(dlg.FileName, type);
}

private void SaveFile()
{
    using var dlg = new SaveFileDialog { Filter = "RTF (*.rtf)|*.rtf|Текст (*.txt)|*.txt" };
    if (dlg.ShowDialog() != DialogResult.OK) return;

    var type = dlg.FilterIndex == 1
        ? RichTextBoxStreamType.RichText
        : RichTextBoxStreamType.UnicodePlainText;

    rtb.SaveFile(dlg.FileName, type);
}

Для русского текста в обычном txt используйте UnicodePlainText, иначе кодировка может не совпасть с ожидаемой. Работу с файлами глубже описывает раздел блога про ввод-вывод.

Поиск и подсветка текста

Метод Find ищет подстроку и сразу выделяет её. Он принимает параметры поиска, например игнорирование регистра или поиск целого слова. Ниже подсветка всех совпадений.

public static int HighlightAll(RichTextBox box, string word, Color color)
{
    if (string.IsNullOrEmpty(word)) return 0;

    int count = 0, start = 0;
    while (start < box.TextLength)
    {
        int index = box.Find(word, start, RichTextBoxFinds.None);
        if (index < 0) break;

        box.SelectionBackColor = color;
        start = index + word.Length;
        count++;
    }
    box.Select(0, 0);
    return count;
}

Чтобы искать без учёта регистра, учтите: флаг RichTextBoxFinds.MatchCase включает регистр, а по умолчанию поиск нечувствителен к нему. Перед новой подсветкой снимите старую, выделив весь текст и вернув SelectionBackColor в цвет фона.

Работа с RTF

Свойство Rtf возвращает документ как строку с управляющими кодами и позволяет перенести форматированный текст из одного поля в другое, сохранить в базе данных или подготовить письмо. Встречается также запись простого RTF вручную.

string rtfText = rtb.Rtf;     // полное содержимое с оформлением
other.Rtf = rtfText;          // копия форматирования в другом поле

// минимальный документ из строки (латиница, без кодирования) (латиница, без кодирования)
rtb.Rtf = @"{\rtf1\ansi{\fonttbl{\f0 Arial;}}\f0\fs24 Plain \b bold\b0  and \i italic\i0 .}";

Кириллицу в ручной записи RTF нужно кодировать управляющими последовательностями вида \'e0 или в Unicode через \uN?. Поэтому для русского текста гораздо проще собирать оформление через свойства Selection, а не писать RTF руками.

События

Для отслеживания действий пользователя подходят несколько событий.

  • TextChanged срабатывает при любом изменении текста, удобно для пометки "документ изменён".
  • SelectionChanged позволяет обновлять панель инструментов: состояние кнопок жирного, размер шрифта, выравнивание.
  • LinkClicked вызывается при клике по ссылке при включённом DetectUrls.
  • KeyDown нужен для горячих клавиш и перехвата Enter.
rtb.DetectUrls = true;
rtb.LinkClicked += (s, e) =>
{
    // для открытия в браузере нужен UseShellExecute
    System.Diagnostics.Process.Start(
        new System.Diagnostics.ProcessStartInfo(e.LinkText!) { UseShellExecute = true });
};

rtb.SelectionChanged += (s, e) =>
{
    statusLabel.Text = $"Позиция: {rtb.SelectionStart}, выделено: {rtb.SelectionLength}";
};

Открывать ссылку из пользовательского текста стоит осторожно: проверяйте, что адрес начинается с http:// или https://, иначе пользователь может запустить произвольную программу.

Вставка только простого текста

По умолчанию вставка из буфера обмена сохраняет форматирование источника, из-за чего в документ попадают чужие шрифты и цвета. Чтобы вставлять чистый текст, перехватывают нажатие Ctrl+V.

rtb.KeyDown += (s, e) =>
{
    if (e.Control && e.KeyCode == Keys.V && Clipboard.ContainsText())
    {
        rtb.SelectedText = Clipboard.GetText();
        e.Handled = true;
        e.SuppressKeyPress = true;
    }
};

Типичные проблемы

  • Строка с оформлением сбрасывается: присвоение Text стирает всё форматирование, а при добавлении текста через += теряются стили. Используйте AppendText.
  • Мерцание при массовом изменении: оформляйте текст пакетом и не вызывайте Select тысячи раз подряд.
  • Исключение при LoadFile: файл не RTF, а тип указан как RichText. Проверяйте расширение.
  • Выход за пределы диапазона в Select: проверяйте, что начало и длина не превышают TextLength.
  • Кнопки жирного не работают: забыли обработать SelectionFont == null при смешанном выделении.
  • Нужен вывод логов из другого потока: обращайтесь к элементу через Invoke, иначе получите исключение о доступе из другого потока.

Альтернативы

RichTextBox подходит для небольших редакторов и журналов. Если нужен полноценный редактор кода с нумерацией строк и подсветкой синтаксиса, берут сторонние компоненты вроде ScintillaNET или FastColoredTextBox. В WPF есть собственный элемент RichTextBox на основе FlowDocument, он гибче, но устроен иначе. Для вывода только для чтения с гиперссылками иногда достаточно WebView2 с HTML.

Частые вопросы

Как изменить цвет части текста в RichTextBox?

Выделите нужный фрагмент методом Select(start, length) и задайте SelectionColor. Для фона используйте SelectionBackColor.

Как добавить текст в конец без потери форматирования?

Вызовите AppendText. Присвоение через Text = Text + ... пересоздаёт содержимое и стирает оформление.

Как сохранить содержимое RichTextBox в файл?

Метод SaveFile(путь, RichTextBoxStreamType.RichText) сохранит с форматированием, а UnicodePlainText запишет обычный текст в Unicode.

Как прокрутить RichTextBox в конец?

Установите курсор в конец через SelectionStart = TextLength и вызовите ScrollToCaret(). Это нужно в окнах журнала.

Как запретить редактирование текста?

Присвойте ReadOnly = true. Выделение и копирование при этом остаются доступными, а цвет фона можно задать явно, чтобы поле не выглядело отключённым.

Можно ли вставить картинку в RichTextBox?

Напрямую свойством нет. Обычно картинку помещают в буфер обмена через Clipboard.SetImage и вставляют методом Paste, либо добавляют изображение в RTF-разметку.