TOC

This article has been localized into Russian by the community.

Элементы управления текстом:

Как создать насыщенный текстовый редактор

Вашему вниманию представляется еще одна статья, вдохновленная тем, насколько крутым является элемент управления RichTextBox и как легко он позволяет создавать небольшой, но достаточно мощный текстовый редактор, наподобие Windows Wordpad! WPF создает все условия для легкой реализации приложения, но, несмотря на это, будет немного больше кода XAML и C#, чем обычно и это нормально. Мы пройдемся по каждому разделу в отдельности, а в конце, я покажу вам весь код целиком.

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

Для начала давайте посмотрим, к чему мы стремимся. Таков должен быть конечный результат:

Интерфейс

Интерфейс состоит из панели инструментов (ToolBar) с кнопками (Button) и полями со списком (ComboBox). Есть кнопки для загрузки и сохранения документа, кнопки для управления толщиной шрифта и его стилем написания, а также два поля со списком для управления семейством и размером шрифта.

Под панелью инструментов (ToolBar) находится элемент управления RichTextBox, в котором будут выполняться все текстовые изменения.

Команды

Первое, что вы можете заметить, это использование команд WPF, которые мы уже обсуждали ранее в этой статье. Мы используем элемент Open (Открыть) и Save (Сохранить) от класса ApplicationCommands, чтобы загрузить и сохранить документ, и мы используем ToggleBold, ToggleItalic и ToggleUnderline из класса EditingCommands для редактирования кнопок.

Преимущество использования команд очевидно, так как элемент управления RichTextBox уже реализует команды ToggleBold, ToggleItalic и ToggleUnderline. Это означает, что нам не нужно писать код, чтобы они работали. Просто подключите их к назначенной кнопке, и всё:

<ToggleButton Command="EditingCommands.ToggleBold" Name="btnBold">
    <Image Source="/WpfTutorialSamples;component/Images/text_bold.png" Width="16" Height="16" />
</ToggleButton>

Таким образом, после подключения команд, мы активировали сочетание клавиш для быстрого редактирования текста. К примеру, Ctrl+B активирует жирный шрифт, Ctrl+I - курсив, а Ctrl+U, подчеркнутый текст.

Обратите внимание, что я использую ToggleButton вместо обычного элемента Button. Я хочу, чтобы кнопка была "проверяемой" - если выбрано выделение полужирным шрифтом, то кнопка будет активна (свойство IsChecked) у ToggleButton. К сожалению, WPF не приспособлен к такому, поэтому нам нужно написать немного кода для обновления состояний кнопки и Combobox. Более подробно мы поговорим об этом чуть позднее.

Команды Open и Save не могут быть обработаны автоматически, поэтому нам придется сделать это, как обычно, с привязкой CommandBinding для окна, а затем обработчиком событий в коде:

<Window.CommandBindings>
    <CommandBinding Command="ApplicationCommands.Open" Executed="Open_Executed" />
    <CommandBinding Command="ApplicationCommands.Save" Executed="Save_Executed" />
</Window.CommandBindings>

Чуть позднее я покажу реализацию этих команд.

Стиль и размер шрифта

Для того, чтобы изменить шрифт и его размер, у нас есть несколько полей со списком (ComboBox). Они заполняются системными шрифтами, а также выбором возможных размеров, например:

public RichTextEditorSample()
{
	InitializeComponent();
	cmbFontFamily.ItemsSource = Fonts.SystemFontFamilies.OrderBy(f => f.Source);
	cmbFontSize.ItemsSource = new List<double>() { 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72 };
}

Благодаря тому, что WPF включает в себя простое создание списка возможных шрифтов, с помощью свойства SystemFontFamilies, мы может с лёгкостью реализовать это. Поскольку список размеров является скорее рекомендацией, то мы делаем этот элемент управления ComboBox редактируемым, чтобы пользователь мог ввести желаемый размер самостоятельно:

<ComboBox Name="cmbFontFamily" Width="150" SelectionChanged="cmbFontFamily_SelectionChanged" />
<ComboBox Name="cmbFontSize" Width="50" IsEditable="True" TextBoxBase.TextChanged="cmbFontSize_TextChanged" />

Также это означает, что мы будем обрабатывать изменения по-разному. Для изменения шрифта в ComboBox мы можем просто обрабатывать событие SelectionChanged, подключаясь к событию TextBoxBase.TextChanged, чтобы обработать, введенный пользователем размер шрифта и его стиль.

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

private void cmbFontFamily_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
	if(cmbFontFamily.SelectedItem != null)
		rtbEditor.Selection.ApplyPropertyValue(Inline.FontFamilyProperty, cmbFontFamily.SelectedItem);
}

private void cmbFontSize_TextChanged(object sender, TextChangedEventArgs e)
{
	rtbEditor.Selection.ApplyPropertyValue(Inline.FontSizeProperty, cmbFontSize.Text);
}

Тут нет ничего особенного. Мы просто передаем выбранное или введенное значение методу ApplyPropertyValue() вместе со свойством, которое мы хотим изменить.

Обновление состояния

Как упоминалось ранее, WPF обрабатывает команды Bold, Italic и Underline самостоятельно, но мы должны вручную обновить состояние этих кнопок, так как команды не могут это осуществить. Поэтому мы также должны обновить два поля со списком (ComboBox), чтобы отразить текущее шрифты и размеры.

Нам необходимо обновить состояние, как только курсор перемещается и/или изменяется выделение, и для этого событие SelectionChanged в RichTextBox идеально подходит. Таким образом мы получаем:

private void rtbEditor_SelectionChanged(object sender, RoutedEventArgs e)
{
	object temp = rtbEditor.Selection.GetPropertyValue(Inline.FontWeightProperty);
	btnBold.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(FontWeights.Bold));
	temp = rtbEditor.Selection.GetPropertyValue(Inline.FontStyleProperty);
	btnItalic.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(FontStyles.Italic));
	temp = rtbEditor.Selection.GetPropertyValue(Inline.TextDecorationsProperty);
	btnUnderline.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(TextDecorations.Underline));

	temp = rtbEditor.Selection.GetPropertyValue(Inline.FontFamilyProperty);
	cmbFontFamily.SelectedItem = temp;
	temp = rtbEditor.Selection.GetPropertyValue(Inline.FontSizeProperty);
	cmbFontSize.Text = temp.ToString();
}

По факту, совсем немного строк, но самую главную работу совершают лишь пара из них. Нам необходимо слегка изменить их, чтобы обновить каждую из трёх кнопок и два поля со списком.

Принцип работы довольно прост. Для кнопок мы используем метод GetPropertyValue(), чтобы получить текущее значение для текстового свойства, например FontWeight, а затем обновляем свойство IsChecked в зависимости от того, является ли возвращаемое значение таким же, как то, что мы хотим или нет.

Для полей со списком (ComboBox) мы делаем то же самое, но вместо установки свойства IsChecked мы задаем свойства SelectedItem или Text, с возвращаемыми значениями.

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

При обработке команд Open и Save мы используем очень похожий код:

private void Open_Executed(object sender, ExecutedRoutedEventArgs e)
{
	OpenFileDialog dlg = new OpenFileDialog();
	dlg.Filter = "Rich Text Format (*.rtf)|*.rtf|All files (*.*)|*.*";
	if(dlg.ShowDialog() == true)
	{
		FileStream fileStream = new FileStream(dlg.FileName, FileMode.Open);
		TextRange range = new TextRange(rtbEditor.Document.ContentStart, rtbEditor.Document.ContentEnd);
		range.Load(fileStream, DataFormats.Rtf);
	}
}

private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
	SaveFileDialog dlg = new SaveFileDialog();
	dlg.Filter = "Rich Text Format (*.rtf)|*.rtf|All files (*.*)|*.*";
	if(dlg.ShowDialog() == true)
	{
		FileStream fileStream = new FileStream(dlg.FileName, FileMode.Create);
		TextRange range = new TextRange(rtbEditor.Document.ContentStart, rtbEditor.Document.ContentEnd);
		range.Save(fileStream, DataFormats.Rtf);
	}
}

OpenFileDialog или SaveFileDialog используется для указания местоположения и имени файла, а затем текст загружается или сохраняется с помощью объекта TextRange, который мы получаем непосредственно из RichTextBox, в сочетании с FileStream, который обеспечивает доступ к физическому файлу. Файл загружается и сохраняется в формате RTF, но вы можете указать любой другой текстовый формат на выбор, к примеру обычный текст (txt).

Полный пример

Ниже представлен код всего приложения, первоначально на XAML, а затем на C#:

<Window x:Class="WpfTutorialSamples.Rich_text_controls.RichTextEditorSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="RichTextEditorSample" Height="300" Width="400">
    <Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.Open" Executed="Open_Executed" />
        <CommandBinding Command="ApplicationCommands.Save" Executed="Save_Executed" />
    </Window.CommandBindings>
    <DockPanel>
        <ToolBar DockPanel.Dock="Top">
            <Button Command="ApplicationCommands.Open">
                <Image Source="/WpfTutorialSamples;component/Images/folder.png" Width="16" Height="16" />
            </Button>
            <Button Command="ApplicationCommands.Save">
                <Image Source="/WpfTutorialSamples;component/Images/disk.png" Width="16" Height="16" />
            </Button>
            <Separator />
            <ToggleButton Command="EditingCommands.ToggleBold" Name="btnBold">
                <Image Source="/WpfTutorialSamples;component/Images/text_bold.png" Width="16" Height="16" />
            </ToggleButton>
            <ToggleButton Command="EditingCommands.ToggleItalic" Name="btnItalic">
                <Image Source="/WpfTutorialSamples;component/Images/text_italic.png" Width="16" Height="16" />
            </ToggleButton>
            <ToggleButton Command="EditingCommands.ToggleUnderline" Name="btnUnderline">
                <Image Source="/WpfTutorialSamples;component/Images/text_underline.png" Width="16" Height="16" />
            </ToggleButton>
            <Separator />
            <ComboBox Name="cmbFontFamily" Width="150" SelectionChanged="cmbFontFamily_SelectionChanged" />
            <ComboBox Name="cmbFontSize" Width="50" IsEditable="True" TextBoxBase.TextChanged="cmbFontSize_TextChanged" />
        </ToolBar>
        <RichTextBox Name="rtbEditor" SelectionChanged="rtbEditor_SelectionChanged" />
    </DockPanel>
</Window>
using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using Microsoft.Win32;
using System.Windows.Controls;

namespace WpfTutorialSamples.Rich_text_controls
{
	public partial class RichTextEditorSample : Window
	{
		public RichTextEditorSample()
		{
			InitializeComponent();
			cmbFontFamily.ItemsSource = Fonts.SystemFontFamilies.OrderBy(f => f.Source);
			cmbFontSize.ItemsSource = new List<double>() { 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72 };
		}

		private void rtbEditor_SelectionChanged(object sender, RoutedEventArgs e)
		{
			object temp = rtbEditor.Selection.GetPropertyValue(Inline.FontWeightProperty);
			btnBold.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(FontWeights.Bold));
			temp = rtbEditor.Selection.GetPropertyValue(Inline.FontStyleProperty);
			btnItalic.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(FontStyles.Italic));
			temp = rtbEditor.Selection.GetPropertyValue(Inline.TextDecorationsProperty);
			btnUnderline.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(TextDecorations.Underline));

			temp = rtbEditor.Selection.GetPropertyValue(Inline.FontFamilyProperty);
			cmbFontFamily.SelectedItem = temp;
			temp = rtbEditor.Selection.GetPropertyValue(Inline.FontSizeProperty);
			cmbFontSize.Text = temp.ToString();
		}

		private void Open_Executed(object sender, ExecutedRoutedEventArgs e)
		{
			OpenFileDialog dlg = new OpenFileDialog();
			dlg.Filter = "Rich Text Format (*.rtf)|*.rtf|All files (*.*)|*.*";
			if(dlg.ShowDialog() == true)
			{
				FileStream fileStream = new FileStream(dlg.FileName, FileMode.Open);
				TextRange range = new TextRange(rtbEditor.Document.ContentStart, rtbEditor.Document.ContentEnd);
				range.Load(fileStream, DataFormats.Rtf);
			}
		}

		private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
		{
			SaveFileDialog dlg = new SaveFileDialog();
			dlg.Filter = "Rich Text Format (*.rtf)|*.rtf|All files (*.*)|*.*";
			if(dlg.ShowDialog() == true)
			{
				FileStream fileStream = new FileStream(dlg.FileName, FileMode.Create);
				TextRange range = new TextRange(rtbEditor.Document.ContentStart, rtbEditor.Document.ContentEnd);
				range.Save(fileStream, DataFormats.Rtf);
			}
		}

		private void cmbFontFamily_SelectionChanged(object sender, SelectionChangedEventArgs e)
		{
			if(cmbFontFamily.SelectedItem != null)
				rtbEditor.Selection.ApplyPropertyValue(Inline.FontFamilyProperty, cmbFontFamily.SelectedItem);
		}

		private void cmbFontSize_TextChanged(object sender, TextChangedEventArgs e)
		{
			rtbEditor.Selection.ApplyPropertyValue(Inline.FontSizeProperty, cmbFontSize.Text);
		}
	}
}

Ниже представлен скриншот, где мы выбрали текст. Обратите внимание, как элементы управления панели инструментов отражают состояние текущего выделения:

Итог

Как вы можете видеть, создать текстовый редактор в WPF очень просто, особенно из-за отличного элемента управления RichTextBox. Если вы хотите, вы можете легко расширить этот пример такими вещами, как выравнивание текста, выбор цвета, списки и даже таблицы.

Имейте в виду, что приведенный выше пример должен работать нормально, нет абсолютно никакой обработки исключений или какой-либо проверки кода. Существуют несколько мест, которые могут легко вызвать ошибки, например, размер шрифта в поле со списком (ComboBox) может вызвать ошибку, если ввести в него не числовое значение. Поэтому, если вы хотите доработать функциональность для дальнейшей работы с этим примером, то вы должны проверить его на ошибки.


This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!