Mastering the WPF RichTextBox

he RichTextBox component in WPF/.NET 3.0 received a major upgrade compared to the previous version of the RichTextBox control that shipped with.NET 2.0. But with the expanded capability comes the need for additional APIs and different usage patterns. This article explores some of the advanced features of the WPF RichTextBox from both a XAML-based design standpoint, and explores how to work with it using C# code.

The Basics
You add a RichTextBox to your form using the XAML code:

   

The x:Name attribute simply gives you a reference to work with that you’ll use later to bind commands to the RichTextBox. MinHeight constrains the minimum size the component will render which is important, because by default renders as a single line?which won’t look right for a sophisticated editing control.

Similar to previous versions of the RichTextBox control, this one supports a wide variety of formatting as shown in Figure 1. And if you have worked with the RichTextBox in .NET 2.0 or even the Windows SDK, you’ll be glad to see the new SpellCheck.IsEnabled attribute. Yes, the RichTextBox not only supports spell checking in WPF, but provides UI support via snazzy inline red squiggles and suggests alternative spellings?just like Microsoft Word (see Figure 2).

?
Figure 1. Good Formatting Support: The figure shows some of the text formatting that the RichTextBox supports, such as underlining, italic, and bold text, as well as advanced features such as spell checking.
?
Figure 2. RichTextBox Spell Checking: Just like Microsoft Word, the RichTextBox underlines misspelled text and provides a popup window that offers alternative spellings.

Unfortunately, just adding the RichTextBox to your form doesn’t give you what you might expect in terms of toolbar buttons to apply bold or italic type, etc. Instead, you’ll need to roll your own UI and hook into the RichTextBox’s Command interface. The upside of the roll your own approach is that it gives you absolute control over the way your interface works. The downside, of course, is that you have to do all the work yourself.

The Command Interface
Microsoft intended developers to work with the RichTextBox component via a Command interface. Although the concept here isn’t new to most folks who have been developing GUIs, the actual implementation and syntax is a little different in XAML-land.

You’ll need to add a ToolBar component (optional but recommended) and some ToggleButton components that set up the binding to your rich text box. The Command attribute on each of these components defines the functionality you want to activate on the RichTextBox, while the CommandTarget defines which RichTextBox you want these buttons to target. Here’s an example that adds a ToolBar and three ToggleButtons:

        B     I            U        

Although the code example included with this article includes only a few command buttons, there are a total of 47 different commands available to choose from. You can see them by checking the EditingCommands class, which behaves just like an enumeration, with IntelliSense. Obviously, from a user-interface standpoint you’ll want to keep the list of commands you support as small as possible while still meeting the needs of your users and the application you’re developing. Presenting the user with 47 icons above a 4-line RichTextBox has some clearly negative implications from a usability standpoint.

Even if you don’t explicitly add bindings to any of the command objects, keyboard shortcuts such as Ctrl-B for bold are still enabled by default. You can disable any of the default enabled commands by adding a CommandBinding. In the snippet below, the attribute CanExecute references an event handler that can incorporate logic to prevent the command from executing:

           

The corresponding event handler in the C# code-behind file sets two properties on the event object. Setting CanExecute to false lets components that are bound to the event know that this option is not currently available. In this case, to block the ToggleBold command, setting CanExecute to false disables the Bold button in the toolbar appear disabled. The second property, Handled, prevents the ToggleBold command from being routed to the RichTextBox:

   protected void BlockTheCommand(object sender,     CanExecuteRoutedEventArgs e)   {     e.CanExecute = false;     e.Handled = true;   }

If you choose to incorporate complex logic in your CanExecute handlers, keep in mind that this event gets fired often, because the UI constantly checks and re-checks to see if the command is available. If you need to access resources such as a database or a Web service to determine if the command should be available, make sure you cache the resource and check it only periodically, or it will destroy your performance.

Getting the Data Out
Now that you’ve got a WPF RichTextBox on your page and supporting some nifty edit commands, you need to find a way to get to the document content that users have entered into the control. Seasoned programmers probably got lockjaw from the moment they realized this was a big enough topic to warrant an entire section. Unfortunately, it does warrant an entire section.

The RichTextBox works with XAML natively to build out all of the text effects you see and enable the capabilities that were absent from previous versions. When you read the value (contents), what you’ll get is XAML code. A large contingent of the application development community probably has all of their rich formatted data stored in HTML format for portability purposes and will have to write their own HTML ( XAML ( HTML conversion routines. Although not complete, a Microsoft Channel 9 blogger created a reference implementation routine for doing just this that you can download . It’s not perfect but it saves you from having to build the routine yourself for simple HTML/XAML conversions.

To get the actual XAML created by the user inside of the RichTextBox, you create a TextRange, and then do some stream processing to read the XAML. The example below uses a memory stream to read the value from the control, and then uses the ASCIIEncoding object to translate it to a string for storage in a database, file, or other data store:

   TextRange tr = new TextRange(      myRichTextBox.Document.ContentStart,      myRichTextBox.Document.ContentEnd);   MemoryStream ms = new MemoryStream();   tr.Save(ms, DataFormats.Xaml);   string xamlString =       ASCIIEncoding.Default.GetString(ms.ToArray());

The preceding code retrieves the entire contents of the RichTextBox by creating a TextRange that starts at the beginning of the document and extends all the way to the end. Although this is more complicated than just getting the value from a Text or Value property it enables you to create much more complex applications using the RichTextBox as a base. You can create smaller text ranges that cover specific portions of a rich text document to develop different functionality.

Modifying the Interface

?
Figure 3. A Date Button: The Date button added to the toolbar inserts the current date into a TextRange.

One common scenario would be to add a button to your control that might get some data from a database and dynamically insert it into the RichTextBox document. This simplified example just inserts the current date.

You use the TextRange object to select what part of the RichTextBox’s document to update. In Figure 3, a toolbar button lets users insert the current date.

In the Click event handler for the button, create a text range based on the current selection, using the Selection.Start and Selection.End properties. Setting the Text property of this TextRange to the current date string replaces whatever text is currently selected. If you just wanted the date to be inserted at the current cursor position without replacing anything, you would create the TextRange using the Selection.Start value for both the starting and ending point of your TextRange.

   protected void InsertCurrentDate      (object sender, EventArgs e)   {      TextRange tr = new TextRange(         XAMLRichBox.Selection.Start,         XAMLRichBox.Selection.End);      tr.Text = DateTime.Now.ToShortDateString();   }

Creative use of TextRange based updating allows multiple users to change the same RichTextBox in real-time though the internet as well as any number of other creative possibilities, such as inline word suggestion similar to IntelliSense.

Modifying the Content
You saw how to modify the content by inserting text in the previous section. In this section, suppose you work for a company called DEVX, and legal is demanding that all company documents include the trademark text (tm) after every occurrence of the word DEVX in a document. You can modify the content of a RichTextBox quickly and simply to achieve this by using regular expressions. For example, the following C# code would execute whenever the user saves the document:

   TextRange tr = new TextRange(      XAMLRichBox.Document.ContentStart,       XAMLRichBox.Document.ContentEnd);   System.IO.MemoryStream ms = new System.IO.MemoryStream();   tr.Save(ms, DataFormats.Xaml);   string xamlString = ASCIIEncoding.Default.GetString(ms.ToArray());   System.Text.RegularExpressions.Regex findDEVX =       new System.Text.RegularExpressions.Regex("DEVX(?![\(TM\)])");   xamlString = findDEVX.Replace(xamlString, "DEVX(tm)");   System.IO.MemoryStream msOut = new System.IO.MemoryStream();   System.IO.StreamWriter sw = new System.IO.StreamWriter(msOut);   sw.Write(xamlString);   sw.Flush();   msOut.Seek(0, System.IO.SeekOrigin.Begin);   tr.Load(msOut, DataFormats.Xaml);

This example extracts the XAML from the RichTextBox by creating a TextRange covering the whole document, which it then saves in XAML format to a MemoryStream. It then converts that memory stream to a text string and applies a regular expression, which finds all instances of DEVX that don’t already have (tm) applied to them. Finally, it writes the modified XAML string back out to another MemoryStream, which is then loaded back into the RichTextBox.

Working with RichTextBox in C#
The RichTextBox control, like all of the other WPF controls, was primarily designed to be worked with in XAML. Despite this orientation it is both possible and useful to create a RichTextBox control in code. For example, using code is useful when you want to simplify configuring a RichTextBox control and a group of related toolbar buttons as a single component.

To create a component that groups controls you’ll want to subclass one of the layout controls, such as a StackPanel, which will allow you to add the toolbar, buttons, and RichTextBox into your composite control. In the constructor, you’ll need to build out the WPF components, adding controls as the toolbar, buttons, and RichTextBox but not the command interface. Unfortunately building out the Command interface in the constructor will result in buttons that do nothing. Despite my best research attempts I’ve been unable to find out why, but if I had to guess I would assume it has something to do with the control lifecycle.

The best solution I’ve been able to find to this issue is to add a Loaded event handler in the constructor that then handles binding the buttons to the RichTextBox control. In the code snippet below, the Bold button in the toolbar gets initialized in the constructor and the code attaches an event handler to the Loaded event. The Loaded event sets up the command bindings by setting the CommandTarget to point to the RichTextBox and the Command to point to the EditingCommands.ToggleBold enumeration.

   ToggleButton bold;   public CustomRichTextBox()   {      //... Initialize the container, toolbar      bold = new ToggleButton();      this.Loaded += new RoutedEventHandler(         CompositeRichTextBox_Loaded);      //...Initialize the rest of the control   }      void CompositeRichTextBox_Loaded(      object sender, RoutedEventArgs e)   {      bold.CommandTarget = myRichTextBox;      bold.Command = EditingCommands.ToggleBold;      //... Initialize the other buttons   }

For further information on how to exploit the power of the RichTextBox control in WPF, I urge you to explore the downloadable code examples included with this article. The examples cover both XAML and C# techniques for wiring together the RichTextBox.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

More From DevX