TOC

This article is currently in the process of being translated into Vietnamese (~41% done).

Rich Text controls:

How-to: Creating a Rich Text Editor

Đây là một bài viết hướng dẫn khác, lấy cảm hứng từ việc điều khiển RichTextBox thú vị như thế nào và cách thức tạo ra dễ dàng làm sao nhưng vẫn có khả năng như trình soạn thảo rich-text : Windows Wordpad! Mặc dù WPF giúp điều này thực sự rất dễ dàng đối với chúng ta, nhưng sẽ có một vài mã lệnh XAML và C# hơn bình thường, tuy nhiên điều đó không vấn đề gì cả. Chúng ta sẽ đi xuyên suốt qua các phần thú vị này một lần, và sau đó đến cuối chương, tôi sẽ gửi bạn toàn bộ mã lệnh trong danh sách

Trong bài viết này, chúng tôi sẽ sử dụng rất nhiều cách điều khiển và kỹ thuật đẫ sử dụng trong các bài viết trước, vì vậy các diễn giải sẽ không quá chi tiết. Trong trường hợp bạn cần chưa hiểu rõ về các phần đó, bạn hoàn toàn có thể quay lại các bài viết trước để hiểu chi tiết hơn

Để bắt đầu, hãy xem xét những gì chúng ta sẽ làm. Đây là kết quả cuối cùng:

Giao diện

Giao diện bao gồm một trình điều khiển ToolBar với các nút ấn và các combo box trong đó. Ở đó có các nút bấm để tải và lưu tài liệu, các nút bấm cho việc điều chỉnh các thuộc tính font-weight và kiểu chữ, và sau đó là hai combo box cho việc điều chỉnh họ và kích thước chữ.

Below the toolbar is the RichTextBox control, where all the editing will be done.

Commands

Điều đầu tiên bạn có thể nhận thấy đó là việc sử dụng các lệnh WPF, điều mà chúng ta đã thảo luận trước đây trong bài viết này. Chúng ta sử dụng lệnh OpenSave từ lớp ApplicationCommand để tải và lưu tài liệu, và chúng ta sử dụng lệnh ToggleBold, ToggleItalic và Toggle Underline từ lớp EditingCommand cho các nút liên quan đến kiểu sử dụng của chúng ta.

Lợi thế của việc sử dụng Commands là một điều hiển nhiên khi trình điều khiển RichTextBox đã tích hợp các lệnh ToggleBold, ToggleItalic và ToggleUnderline. Điều này có nghĩa rằng chúng ta không cần phải viết bất kỳ một mã lệnh nào về chúng để làm việc. Chỉ cần kết nối chúng vào nút ấn chỉ định và nó sẽ hoạt động.

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

Chúng ta cũng có một số các phím tắt như sau - bấm Ctrl+B để bật tắt chữ bôi đậm, Ctrl+I để bật tắt chữ viết nghiêng và Ctrl+U để bật tắt chữ gạch dưới

Lưu ý rằng tôi đang sử dụng kiểu điều khiển ToggleButton thay vì sử dụng kiểu điều khiển Button như thông thường. Tôi muốn nút bấm này có thể kiểm tra được, nếu viêc lựa chọn hiện tại là in đâm, và được hỗ trợ thông qua thuộc tính IsChecked của ToggleButton. Tuy nhiên, WPF lại không có cách nào xử lý phần này cho chúng ta, vì vậy chúng ta cần một chút mã lệnh để cập nhật các trạng thái khác nhau của nút bấm và combo box. Các thông tin này sẽ được nói tiếp trong phần sau.

The Open and Save commands can't be handled automatically, so we'll have to do that as usual, with a CommandBinding for the Window and then an event handler in Code-behind:

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

I'll show you the implementation later in this article.

Font family and size

To show and change the font family and size, we have a couple of combo boxes. They are populated with the system font families as well as a selection of possible sizes in the constructor for the window, like this:

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 };
}

Once again, WPF makes it easy for us to get a list of possible fonts, by using the SystemFontFamilies property. Since the list of sizes is more of a suggestion, we make that ComboBox control editable, so that the user may enter a custom size:

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

This also means that we'll be handling changes differently. For the font family ComboBox, we can just handle the SelectionChanged event, while we hook into the TextBoxBase.TextChanged event of the size ComboBox, to handle the fact that the user can both select and manually enter a size.

WPF handles the implementation of the Bold, Italic and Underline commands for us, but for font family and size, we'll have to manually change these values. Fortunately, it's quite easy to do, using the ApplyPropertyValue() method. The above mentioned event handlers look like this.

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);
}

Nothing too fancy here - we simply pass on the selected/entered value to the ApplyPropertyValue() method, along with the property that we wish to change.

Updating the state

As previously mentioned, WPF handles the Bold, Italic and Underline commands for us, but we have to manually update the state of their buttons, since this is not a part of the Commands functionality. However, that's okay, since we also have to update the two combo boxes to reflect the current font family and size.

We want to update the state as soon as the cursor moves and/or the selection changes, and for that, the SelectionChanged event of the RichTextBox is perfect. Here's how we handle it:

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();
}

Quite a bit of lines, but the actual job only requires a couple of lines - we just repeat them with a small variation to update each of the three buttons, as well as the two combo boxes.

The principle is quite simple. For the buttons, we use the GetPropertyValue() method to get the current value for a given text property, e.g. the FontWeight, and then we update the IsChecked property depending on whether the returned value is the same as the one we're looking for or not.

For the combo boxes, we do the same thing, but instead of setting an IsChecked property, we set the SelectedItem or Text properties directly with the returned values.

Loading and saving a file

When handling the Open and Save commands, we use some very similar code:

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);
	}
}

An OpenFileDialog or SaveFileDialog is used to specify the location and filename, and then the text is either loaded or saved by using a TextRange object, which we obtain directly from the RichTextBox, in combination with a FileStream, which provides the access to the physical file. The file is loaded and saved in the RTF format, but you can specify one of the other format types if you want your editor to support other formats, e.g. plain text.

The complete example

Here's the code for the entire application - first the XAML and then the C# Code-behind:

<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);
		}
	}
}

And here's another screenshot where we've selected some text. Notice how the toolbar controls reflects the state of the current selection:

Summary

As you can see, implementing a rich text editor in WPF is very simple, especially because of the excellent RichTextBox control. If you want, you can easily extend this example with stuff like text alignment, colors, lists and even tables.

Please be aware that while the above example should work just fine, there's absolutely no exception handling or checking at all, to keep the amount of code to a minimum. There are several places which could easily throw an exception, like the font size combo box, where one could cause an exception by entering a non-numeric value. You should of course check all of this and handle possible exceptions if you wish to expand on this example for your work.

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!