26 Oct
2010

Create your own Format Bar for the WPF RichTextBox

Category:UncategorizedTag: , , :

Have you ever tried to use the WPF RichTextBox?  If you did try to use it, did you pull your hair out trying to figure out how to accomplish the simplest of tasks?  Don?t panic, your not the only one.  I often get asked tons of questions on how to use the WPF RichTextBox.  Lets be honest, it is not the easiest control to figure out. 

Due to the overwhelming responses I have been getting, I will outline the most common ways to use the RichTextBox by showing you step-by-step how to build your own custom format toolbar for the RichTextBox.  By the end of this post you should be a RichTextBox master!

First lets define some of the actions we want to impose on our RichTextBox.  First, I want to be able to do all major editing functions such as cut, copy, paste, undo, redo, select all, and clear.  I also want to be able to manipulate anything to do with fonts, including changing the font family, font size, bold, italics, and underline.  Heck I also want to be able to create bulleted lists and numbered lists, align text left, right, center, and justified.  I think that covers the majority of tasks we want to do.

Lets start off by creating our UI.  Because I want to be trendy, I will use the Microsoft WPF Ribbon control.  If you don?t have it yet, get it here.  Since we are using the ribbon we have to think about how we are going to group our menu items.  This is what my UI looks like.  I am not going to paste the XAML because you can get it from the download at the end of the post.

image

Commands

Now that we have our UI figured out we need to find a way to get our buttons to manipulate the text in our RichTextBox.  One approach would be to create an event handler for every single button in our ribbon, and write code accordingly.  Or, we can just use Commands.  If you are not familiar with commands, I suggest you read up on them.  Luckily for us there are built in commands to do the majority of what we want to do without having to write a single line of code.  The two command classes we are interested in are the ApplicationCommands and the EditingCommands.  To associate a command with a button is quite easy.

 

<my:RibbonToggleButton x:Name="_btnBold"
?????????????????????? SmallImageSource="/FormatBarForRichTextBox;component/Images/Bold16.png"
?????????????????????? Command="{x:Static EditingCommands.ToggleBold}" CommandTarget="{Binding ElementName=_richTextBox}">

 

The two important things to notice in this code block is the Command and the CommandTarget.  The Command is the command we want to execute when the button is clicked. In this case we want to toggle the Bold of the text when the button is click so we use the EditingCommand.ToggleBold command.  Well this is all fine and dandy, but if you tried to use it ?as is? nothing would happen.  You have to specify which control you want the command to apply to.  You do this by setting the CommandTarget property to the control you want to apply the command to, in this case our RichTextBox control named _richTextBox.

Lets go ahead and follow this logic with the rest of our buttons.  Using the different commanding classes I will know which one to use for the different operations I want to perform.

image

Now the majority of our buttons function.  As you click the different buttons, the corresponding command will execute on the RichTextBox.  You will notice that the button in the ?Edit? group will enable and disable automatically.  The font and font size we will get to later.

Saving Button State

Now you have the majority of your buttons working perfectly without writing any code-behind.  But as you start clicking around will notice almost immediately that your buttons states get all screwed up.  If I click ?Bold? the move to different text that isn?t bolded, the ?Bold? button is still in the toggled state.  The same occurs with all the formatting buttons.  Why is this happening?  Well command are great and save you a lot of time and give you great functionality, but they do not do everything for you.  Commands will not remember or control the coordination of state across the different buttons.  To do this you have to write some code.

First, create an event handler for the RichTextBox?s SelectionChanged event.  We want to handle this event because anytime new text is selected we want to update our UI to reflect the state of that particular text.

Let use the Bold toggle button as a basis for all other buttons.  Lets look at the code then discuss the vairous parts.

private void RichTextBox_SelectionChanged(object sender, RoutedEventArgs e)
????{
????????object currentValue = _richTextBox.Selection.GetPropertyValue(TextElement.FontWeightProperty);
????????_btnBold.IsChecked = (currentValue == DependencyProperty.UnsetValue) ? false : currentValue != null && currentValue.Equals(FontWeights.Bold);;
????}

 

The key to getting any style/modification that have been applied to the RichTextBox is the RichTextBox.Selection.GetPropertyValue method.  The Selection property as you can guess give you the current text selection.  Next by using the GetPropertyValue method we can pass in different types of dependency properties to get what we are looking for.  In this example I am getting the FontWeight of the selected text and checking to see if it is ?Bold?.  Then I am using a ternary operation to change the state of the button depending on the resulting value.  This same concept will be used for the remainder of our toggle buttons.

Selecting the ?Bolded? text shows the toggle button in the Checked state.

image

Selecting any other non-bolded text shows the Bold toggle button in the unchecked state.

image

So now that we know we have more buttons to deal with, lets refactor this into a reusable method.

void UpdateItemCheckedState(ToggleButton button, DependencyProperty formattingProperty, object expectedValue)
{
????object currentValue = RichTextBox.Selection.GetPropertyValue(formattingProperty);
????button.IsChecked = (currentValue == DependencyProperty.UnsetValue) ? false : currentValue != null && currentValue.Equals(expectedValue);
}

Using this method, I can add state checking to any button with a check state.  Now I have a method that looks like this.

private void UpdateToggleButtonState()
{
????UpdateItemCheckedState(_btnBold, TextElement.FontWeightProperty, FontWeights.Bold);
????UpdateItemCheckedState(_btnItalic, TextElement.FontStyleProperty, FontStyles.Italic);
????UpdateItemCheckedState(_btnUnderline, Inline.TextDecorationsProperty, TextDecorations.Underline);

????UpdateItemCheckedState(_btnAlignLeft, Paragraph.TextAlignmentProperty, TextAlignment.Left);
????UpdateItemCheckedState(_btnAlignCenter, Paragraph.TextAlignmentProperty, TextAlignment.Center);
????UpdateItemCheckedState(_btnAlignRight, Paragraph.TextAlignmentProperty, TextAlignment.Right);
????UpdateItemCheckedState(_btnAlignJustify, Paragraph.TextAlignmentProperty, TextAlignment.Right);
}

 

List Buttons

This handle most of our buttons.  Next, lets move on to the List buttons.  List are a little different.  First you have to get the entire selection from start to end.  Then you check to see if the selection?s parent is ListItem.  If it is you grab the MarkerStyle and check it against the TextMarkerStyle enum.  The following method accomplishes what we want.

private void UpdateSelectionListType()
{
????Paragraph startParagraph = _richTextBox.Selection.Start.Paragraph;
????Paragraph endParagraph = _richTextBox.Selection.End.Paragraph;
????if (startParagraph != null && endParagraph != null && (startParagraph.Parent is ListItem) && (endParagraph.Parent is ListItem) && object.ReferenceEquals(((ListItem)startParagraph.Parent).List, ((ListItem)endParagraph.Parent).List))
????{
????????TextMarkerStyle markerStyle = ((ListItem)startParagraph.Parent).List.MarkerStyle;
????????if (markerStyle == TextMarkerStyle.Disc) //bullets
????????{
????????????_btnBullets.IsChecked = true;
????????}
????????else if (markerStyle == TextMarkerStyle.Decimal) //numbers
????????{
????????????_btnNumbers.IsChecked = true;
????????}
????}
????else
????{
????????_btnBullets.IsChecked = false;
????????_btnNumbers.IsChecked = false;
????}
}

 

image

 

Font Family and Size

Things are working out nicely.  Now the font family and font size involve a little more code.  To get a list of font familys we will just use the System.Windows.Media.Fonts.SystemFontFamilies class.  Our font sizes will just be a simple array of doubles.  You may have code that is similar to the following.

void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
????_fontFamily.ItemsSource = System.Windows.Media.Fonts.SystemFontFamilies;
????_fontSize.ItemsSource = FontSizes;
}

public double[] FontSizes
{
????get
????{
????????return new double[] {
????????????3.0, 4.0, 5.0, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5,
????????????10.0, 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 15.0,
????????????16.0, 17.0, 18.0, 19.0, 20.0, 22.0, 24.0, 26.0, 28.0, 30.0,
????????????32.0, 34.0, 36.0, 38.0, 40.0, 44.0, 48.0, 52.0, 56.0, 60.0, 64.0, 68.0, 72.0, 76.0,
????????????80.0, 88.0, 96.0, 104.0, 112.0, 120.0, 128.0, 136.0, 144.0
????????????};
????}
}

 

We have to create event handlers for the SelectionChanged events for our combo boxes to apply the selected font family and size to our text.  The code for both event handlers will look something like the following.

private void FontFamily_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
????FontFamily editValue = (FontFamily)e.AddedItems[0];
????ApplyPropertyValueToSelectedText(TextElement.FontFamilyProperty, editValue);
}

private void FontSize_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
????ApplyPropertyValueToSelectedText(TextElement.FontSizeProperty, e.AddedItems[0]);
}

void ApplyPropertyValueToSelectedText(DependencyProperty formattingProperty, object value)
{
????if (value == null)
????????return;

????_richTextBox.Selection.ApplyPropertyValue(formattingProperty, value);
}

 

Now lets create methods to have our combo boxes select the currently applied font family and font sizes.

private void UpdateSelectedFontFamily()
{
????object value = _richTextBox.Selection.GetPropertyValue(TextElement.FontFamilyProperty);
????FontFamily currentFontFamily = (FontFamily)((value == DependencyProperty.UnsetValue) ? null : value);
????if (currentFontFamily != null)
????{
????????_fontFamily.SelectedItem = currentFontFamily;
????}
}

private void UpdateSelectedFontSize()
{
????object value = _richTextBox.Selection.GetPropertyValue(TextElement.FontSizeProperty);
????_fontSize.SelectedValue = (value == DependencyProperty.UnsetValue) ? null : value;
}

 

Now anytime test is selected the corresponding font and font size will be selected in the combo boxes.

image

Conclusion

We have covered the most common scenarios for formatting the text in the RichTextBox.  I know I haven?t covered them all, but I feel these will get you going in the right direction.  The key thing to remember is Commands.  Commands give you a lot of power with little or zero code when dealing with the RichTextBox.  Of course managing the button state is a different story.  Go download the source and have fun.

Download the Source Code

Note: If you are looking for a RichTextBox that you can databind and control the format of the text, be sure to check out the WPF Extended Toolkit on CodePlex.

9 thoughts on “Create your own Format Bar for the WPF RichTextBox

  1. I think I can guess at cause for questions about the rich text control, it really isn’t any different than working with its previous incarnations going back to VB.

    I believe some camps may be expecting an (optional) integrated toolbar of common applicable features by now. The other major camp would be the one conditioned to believe that in the world of WPF *all* code-behind is evil so they’re trying to figure out how to accomplish that just with data binding.

  2. If I have multiple rich text boxes on one form and one set of toggle buttons/dropdowns for formatting, how would you suggest to apply font family/size changes?  Also, how do you keep the control states in sync as the user changes from one rich text box to another.

    1. My first thought would be to create a dependency property that represents the current focused RichTextBox.  So when the property changes you can change the command target of the buttons.  then to keep things in sync use the property instead of the named RTB.  Not tested of course, just a though.

Comments are closed.