Элемент RichTextBox

99

Если вы программировали в WPF, вам должна быть знакома модель потока документа — гибкая система отображения форматированного текста в режиме чтения. По сравнению с обычным отображением текста (например, в элементе TextBox) поток документа поддерживает более мощные средства отображения, такие как балансирование колонок, обтекание фигуры текстом, гибкий алгоритм установки расстояний между символами и перенос текста с помощью дефисов. В Silverlight нет поддержки потоковых документов, но есть наиболее важная часть этой модели — элемент RichTextBox.

Редактируемый элемент RichTextBox поддерживает форматирование. В отличие от элемента TextBox он позволяет по-разному форматировать отдельные фрагменты (слово, часть слова, часть абзаца), присваивая им разные шрифты, цвета, размеры и т.п. Кроме того, элемент RichTextBox поддерживает вставку изображений, гиперссылок и встроенных элементов (таких, как раскрывающиеся списки и кнопки). К тому же элемент RichTextBox легко использовать в любом приложении Silverlight.

Элементы текста

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

Элементы текста существенно отличаются от элементов интерфейса тем, что они не наследуют хорошо знакомые вам классы UIElement и FrameworkElement. Вместо этого они входят в отдельную ветвь классов, производных сначала от DependencyObject, а затем — от TextElement. Эти базовые классы много проще, чем классы элементов интерфейса. Они не поддерживают событий и предоставляют только небольшой набор свойств, имеющих отношение главным образом к форматированию. Ниже показана иерархия наследования элементов текста:

Элементы текста

Существуют две ключевые ветви элементов текста:

Блочные элементы

Определены два класса блочных элементов: Paragraph (Абзац) и Section (Раздел). Элемент абзаца может содержать текст и комбинации встроенных элементов. Раздел может содержать группу абзацев или разделов. Его можно создать только программно, в XAML разделы не используются.

Строчные элементы

Строчные элементы вложены в блочный элемент или другой встроенный элемент. В эту ветвь входят элементы форматирования текста (Bold, Italic, Underline и Run), разрыва строки (LineBreak), добавления гиперссылки (Hyperlink) и внедрения других элементов управления (InlineUIContainer). Контейнер Span предоставляет возможность сгруппировать несколько строчных элементов.

В рассматриваемой модели элементов текста допустимы несколько уровней вложенности. Например, элемент Bold можно вложить в элемент Underline, тогда текст будет одновременно полужирным и подчеркнутым. Аналогично можно создать элемент Section, служащий оболочкой для нескольких элементов Paragraph, каждый из которых содержит множество встроенных элементов с реальным текстовым содержимым. Все эти элементы определены в пространстве имен System.Windows.Documents.

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

Рассмотрим пример вывода в элементе RichTextBox документа, жестко закодированного в разметке XAML:

<RichTextBox Margin="5" x:Name="richText">
        <Paragraph Foreground="DarkRed" FontFamily="Trebuchet MS" FontSize="22"
                   FontWeight="Bold" TextAlignment="Center">Глава 1</Paragraph>
        <Paragraph TextAlignment="Center" >
            <LineBreak/>
            <Bold>
                <Italic>
                    <Run FontSize="12">Долгожданные гости</Run>
                </Italic>
            </Bold>
            <LineBreak/>
        </Paragraph>
        <Paragraph>
            В Хоббитоне был переполох...
            <LineBreak/>
        </Paragraph>
        <Paragraph>
            Но пока об оплате не было и речи...
            <LineBreak/>
        </Paragraph>
        ...
</RichTextBox>

Этого примера достаточно для иллюстрации ключевых деталей модели элементов текста. Из него видно, что каждый элемент RichTextBox содержит коллекцию блочных элементов, в данном случае — четыре объекта абзаца, содержащих вложенные объекты Bold, Italic и Run. Элемент LineBreak создает пространство между абзацами, чтобы облегчить чтение документа:

Простой форматированный документ

Форматирование элементов текста

Применение форматов к элементам текста может выполняться двумя способами. Первый состоит в использовании свойств, определенных в классе элемента текста. Большинство из этих свойств унаследовано от базового класса TextElement. Этот способ применяется в предыдущем примере для форматирования названия главы в первом абзаце и класса Run во втором абзаце. Все доступные свойства перечислены в таблице ниже:

Свойства содержимого
Свойство Описание
Foreground Принимает кисти, которые будут применяться для прорисовки переднего плана текста. Свойства Background у элементов текста нет, поэтому установить фон для отдельных элементов текста нельзя, но можно установить фон всего документа с помощью свойства RichTextBox.Background
FontFamily, FontSize, FontStretch, FontStyle и FontWeight Конфигурирование шрифта, используемого для отображения текста
TextAlignment Выравнивание вложенного содержимого по горизонтали. Может принимать значения Left (по левому краю), Right (по правому краю), Center (по центру) и Justify (по обоим краям)
TextDecoration Добавление подчеркивания. Это свойство доступно только для встроенных объектов

Второй способ состоит в использовании вложенного элемента (такого как Bold, Italic или Underline), который применяет форматирование автоматически. В предыдущем примере этот способ используется во втором абзаце.

Фактически второй способ (использование форматирующих элементов) является подмножеством первого (использование форматирующих свойств). Форматирующий элемент — это всего лишь более удобная сокращенная запись объекта Run или Span с тем же форматированием. Например, добавление элемента Bold эквивалентно присвоению свойству FontStyle значения Bold. Аналогично добавление элемента Italic эквивалентно присвоению свойству FontStyle значения Italic.

По сравнению с потоковыми документами WPF элементы текста в Silverlight менее мощные вследствие отсутствия в них нескольких средств. Например, с их помощью нельзя увеличить или уменьшить пространство над или под абзацем. В них нет алгоритмов выравнивания символов, обтекания фигур текстом и т.п.

Создание текстового редактора

Документ можно создать в разметке XAML или с помощью кода C#, но реальная польза элементов RichTextBox состоит в том, что они служат местом, где пользователь может редактировать форматированное содержимое.

С точки зрения редактирования содержимого элемент RichTextBox работает так же, как TextBox. Пользователь может вводить в окне новый текст, редактировать существующий, выделять фрагменты, вырезать и вставлять выделенные фрагменты и т.п. Однако встроенных средств форматирования в RichTextBox нет. Чтобы пользователь мог форматировать текст, следует добавить в интерфейс нужные дли этого элементы управления, например кнопку, переключающую обычный шрифт на полужирный, или раскрывающийся список, содержащий доступные шрифты.

Ниже показан пример такого редактора:

Текстовый редактор на Silverlight

В данном примере пользователь выделяет непрерывный фрагмент текста (выделение может охватить несколько встроенных элементов и даже несколько абзацев) и щелкает на кнопках форматирования. Выделенный текст форматируется соответствующим образом.

Также в этом текстовом редакторе добавлена возможность сохранять отформатированный текст в файл, загружать текст из файла и создавать новый документ. В Silverlight не встроена поддержка форматов .rtf, .doc и .docx, однако с помощью свойства RichTextBox.Xaml отформатированное содержимое можно извлечь и сохранить как документ XAML.

Ниже приведена разметка страницы текстового редактора и показан код:

<UserControl x:Class="SilverlightTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="2*"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" Background="Gray">
            <StackPanel.Resources>
                <Style TargetType="Button">
                    <Setter Property="Padding" Value="5"/>
                    <Setter Property="Margin" Value="3,5"/>
                </Style>
            </StackPanel.Resources>
            <Button x:Name="cmdNew" Content="Создать" Margin="5,5,3,5" Click="cmdNew_Click"/>
            <Button x:Name="cmdOpen" Content="Открыть" Click="cmdOpen_Click"/>
            <Button x:Name="cmdSave" Content="Сохранить" Click="cmdSave_Click"/>
            <Button x:Name="cmdBold" Content="B" FontWeight="Bold" Click="cmdFormatting_Click" Margin="20,5,3,5"/>
            <Button x:Name="cmdItalic" Content="I" FontStyle="Italic" Click="cmdFormatting_Click"/>
            <Button x:Name="cmdUnder" Click="cmdFormatting_Click">
                <TextBlock TextDecorations="Underline">U</TextBlock>
            </Button>
            <Button x:Name="cmdXaml" Content="Показать код XAML" Margin="20,5,3,5" Click="cmdXaml_Click"/>
        </StackPanel>
        <RichTextBox x:Name="richText" Grid.Row="1">
            <Paragraph Foreground="DarkRed" FontFamily="Trebuchet MS" FontSize="22"
                   FontWeight="Bold" TextAlignment="Center">Глава 1</Paragraph>
            <Paragraph TextAlignment="Center" >
                <LineBreak/>
                <Bold>
                    <Italic>
                        <Run FontSize="12">Долгожданные гости</Run>
                    </Italic>
                </Bold>
                <LineBreak/>
            </Paragraph>
            <Paragraph>
                В Хоббитоне был переполох...
                <LineBreak/>
            </Paragraph>
            <Paragraph>
                Но пока об оплате не было и речи...
                <LineBreak/>
            </Paragraph>
            <Paragraph>
                Любимцем Бильбо...
                <LineBreak/>
            </Paragraph>
        </RichTextBox>
        <TextBox x:Name="editText" TextWrapping="Wrap" Grid.Row="2"/>
    </Grid>
</UserControl>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.IO;

namespace SilverlightTest
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void cmdNew_Click(object sender, RoutedEventArgs e)
        {
            // Просто очищаем оба текстовых поля
            richText.Blocks.Clear();
            editText.Text = "";
        }

        private void cmdOpen_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog openFile = new OpenFileDialog();
            openFile.Filter = "Файлы XAML (*.xaml)|*.xaml";

            string content = "";
            if (openFile.ShowDialog() == true)
            {
                // Читаем файл в переменную content используя поток чтения StreamReader
                using (StreamReader reader = openFile.File.OpenText())
                {
                    content = reader.ReadToEnd();
                }

                richText.Xaml = content;
                editText.Text = "";
            }    
        }

        private void cmdSave_Click(object sender, RoutedEventArgs e)
        {
            SaveFileDialog saveFile = new SaveFileDialog();
            saveFile.Filter = "Файлы XAML (*.xaml)|*.xaml";

            if (saveFile.ShowDialog() == true)
            {
                // Переписываем и сохраняем файл на жестком диске
                using (FileStream fs = (FileStream)saveFile.OpenFile())
                {
                    System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
                    byte[] buffer = enc.GetBytes(richText.Xaml);
                    fs.Write(buffer, 0, buffer.Length);
                }
            }
        }

        private void cmdFormatting_Click(object sender, RoutedEventArgs e)
        {
            // Ссылка на объект выделения
            TextSelection selection = richText.Selection;

            // Если текст не найден, код интерпретирует
            // шрифт как обычный
            FontWeight weightState = FontWeights.Normal;
            FontStyle styleState = FontStyles.Normal;
            TextDecorationCollection currentState = null;

            if (e.OriginalSource == cmdBold)
            {
                // Проверка, выведен ли фрагмент полужирным цветом
                if (selection.GetPropertyValue(Run.FontWeightProperty) !=
                    DependencyProperty.UnsetValue)
                {
                    weightState = (FontWeight)selection.GetPropertyValue(
                        Run.FontWeightProperty);
                }

                if (weightState == FontWeights.Normal)
                {
                    selection.ApplyPropertyValue(Run.FontWeightProperty, FontWeights.Bold);
                }
                else
                {
                    selection.ApplyPropertyValue(Run.FontWeightProperty, FontWeights.Normal);
                }
            }

            if (e.OriginalSource == cmdItalic)
            {
                // Проверка, выведен ли фрагмент наклонным стилем
                if (selection.GetPropertyValue(Run.FontStyleProperty) != DependencyProperty.UnsetValue)
                    styleState = (FontStyle)selection.GetPropertyValue(Run.FontStyleProperty);

                if (styleState == FontStyles.Italic)
                {
                    selection.ApplyPropertyValue(Run.FontStyleProperty, FontStyles.Normal);
                }
                else
                {
                    selection.ApplyPropertyValue(Run.FontStyleProperty, FontStyles.Italic);
                }
            }

            if (e.OriginalSource == cmdUnder)
            {
                // Проверка, выведен ли фрагмент с подчеркиванием
                if (selection.GetPropertyValue(Run.TextDecorationsProperty) != DependencyProperty.UnsetValue)
                    currentState = (TextDecorationCollection)selection.GetPropertyValue(Run.TextDecorationsProperty);

                if (currentState != TextDecorations.Underline)
                {
                    selection.ApplyPropertyValue(Run.TextDecorationsProperty, TextDecorations.Underline);
                }
                else
                {
                    selection.ApplyPropertyValue(Run.TextDecorationsProperty, null);
                }
            }

            // Возврат фокуса полю, чтобы пользователь мог продолжить работу с ним
            richText.Focus();
        }

        private void cmdXaml_Click(object sender, RoutedEventArgs e)
        {
            editText.Text = richText.Xaml;
        }
    }
}

Вставка интерактивных элементов в RichTextBox

Класс RichTextBox поддерживает не только отображение и редактирование форматированного содержимого, но и некоторые интерактивные элементы, такие как Hyperlink.

Объект Hyperlink — это встроенный элемент текста, выводимый синим цветом с подчеркиванием. Он работает так же, как кнопка HyperlinkButton. Как и HyperlinkButton, объект Hyperlink может запустить веб-браузер для вывода внешней страницы (если свойству NavigateUri присвоен абсолютный URL-адрес) или перенаправить фрейм приложения на другую страницу XAML (если свойству NavigateUri присвоен относительный URL-адрес). Присоединив обработчик к событию Click, можно задать вызов процедуры в коде и выполнение нужных операций при щелчке на гиперссылке.

Учитывайте, что в документе гиперссылки активны, только когда свойству RichTextBox.IsReadOnly присвоено значение true. В противном случае гиперссылка выглядит так же, но щелчок на ней ни к чему не приводит. Редактировать гиперссылку можно только в режиме чтения и редактирования.

Объект Hyperlink можно добавлять непосредственно в текст элемента RichTextBox. Добавлять непосредственно в текст другие элементы Silverlight нельзя. Однако можно добавить встраиваемый контейнер InlineUIContainer, который может содержать любой элемент Silverlight, например Button, CheckBox, Image или даже DataGrid. Ниже показано добавление изображения в поле RichTextBox:

<RichTextBox x:Name="richText" Grid.Row="1">
     <Paragraph TextAlignment="Center">
          <InlineUIContainer>
              <Image Source="poster.jpg" Width="200" Height="300"/>
          </InlineUIContainer>
          <LineBreak/>
     </Paragraph>
            ...

Результат показан на рисунке ниже. Обратите внимание на то, что встроенные элементы располагаются в строке текста, будто это обычные символы. Это означает, что несколько строк текста не могут обтекать большой встроенный элемент, такой как изображение.

Встраивание картинки в RichTextBox

Объекту типа InlineUIContainer присуще существенное ограничение - если внедренный элемент должен быть интерактивным (т.е. получать фокус и реагировать на события ввода), его свойству RichTextBox.IsReadOnly нужно присвоить значение true. Если не сделать этого, пользователь будет видеть элемент и редактировать содержимое RichTextBox.

Пройди тесты
Лучший чат для C# программистов