Click here to Skip to main content
15,884,836 members
Articles / Desktop Programming / WPF
Article

WPF RichText Editor with Slider Formatting and a Font Style Dialog

Rate me:
Please Sign up or sign in to vote.
4.75/5 (5 votes)
5 Jul 2009CPOL9 min read 63.3K   4.1K   42   2
WPF RichText Editor with custom Slider Control formatting and a Font Style Dialog box
Revolution_in_Time_formatted

Note

Many projects are essentially finished components that can be added to your existing project with just a little work. Be forewarned, this submission is more like a messy lab experiment. I'm trying to figure out how to use WPF to make richtext document formatting easier. We have new WPF tools. How do we leverage these tools to make the user experience more productive? Unless you are a strong WPF coder, I would not recommend implementing this code in your project until it is a more mature piece of work.

Introduction

One of my pet peeves when it comes to richtext editors is the need to continually move the mouse back and forth between the menu/toolbar and the editor window during formatting. This sample editor experiments with slider controls and a font/color combobox in an effort to simplify the document layout process.

From a programming standpoint, this project makes heavy use of Josh Smith’s WPF drag-n-drop program (see credits), custom controls, and observablecollections. There are two main windows: the RichText editor and a fontstyle dialog. I have purposely left out several desirable features in the editor to keep the focus on the main design goals. The code is heavily documented and assumes that the reader is somewhat new to WPF.

The text ruler with its sliders automatically applies layout changes to the paragraph that contains the edit carat. Just click on the paragraph and start adjusting the sliders. In WPF, lists (and their listitems) are made up of paragraphs. I ignore paragraph processing for lists and adjust all but the indent slider for every paragraph in the list. When working with lists, the indent slider does nothing. There is no need to establish a selected text range for lists or paragraphs. Font/color changes, on the other hand, are applied to a selected text range.

Technical Keywords

  • Configuration Settings in Apps.Config
  • Listview Drag-n-Drop with System Fonts
  • Regular Expressions
  • Custom WPF Slider Controls
  • WPF Paragraph and List level text formatting in the RichText Editor
  • ListView observablecollections with object based items

Using the Code

The Text Ruler

The text ruler is a simple grid with 5 custom sliders. The ruler image is just a 96 dpi JPEG: ImageSource="Resources\RulerIII.jpg". If you don't like the image I created, just create a 672x42 image (I used GIMP). Put your image into the resources folder and change the ImageSource in the XAML to point to your work of art. Just remember it’s 96 pixels to the inch and the XAML viewport is expecting a 672x42 image. I added 12 pixels to the main grid width to account for the vertical scroll bar in the RichText Editor.

XML
<Grid Margin="10,0,22,0" Name="stackPanel1" VerticalAlignment="Top" 
	HorizontalAlignment="left"  Height="37" Width="684" Grid.Row="1">
    <Grid.Background>
        <ImageBrush  ImageSource="Resources\RulerIII.jpg" 
	Opacity="1" TileMode="tile" ViewportUnits="Absolute" Viewport="6,0,670,48"/>
    </Grid.Background>

The lower left and right sliders adjust the current paragraph’s margins. The upper left slider adjusts the text-indent value. When you change the left margin, the text-indent slider value is also adjusted. The two sliders on the upper right adjust font size and line-spacing respectively. The WPF richtextbox automatically increases the line-spacing as the font size increases. The line-space min/max value range in the control should adjust with the font size but it is currently fixed. When working with large fonts, the line-space slider has no effect when the thumb is on the left (lower) side of the slider range. It doesn’t affect the layout of the document it only limits the usefulness of the feature. I put it on my list of things to do.

When the carat in the richtext editor is moved to a new paragraph (or list item), the code automatically adjusts the sliders to match the XAML<paragraph> values. I have not yet found a way to add <Floater> entries via the user interface but I did include one in the sample document to make sure I understood how the program would react to its presents.

The slider controls are all custom but all I changed was the size of the slider thumbs. Each slider is linked via the XAML to a ‘commandslider’ object. There is probably a way to use a single commandslider class and just create 5 instances. I want to be able to customize the code for each slider in the future and that requires one class per slider. Don't assume too much from my preference - it's just the way I wanted to proceed.

Each of the sliders uses dependency properties. The use of dependency properties assures the programmer that there is only one source of information for each slider control. How the state values get changed DEPENDS on the methods used and their priorities but there is only one set of properties and one set of values for a given control. When programming a complex user interface - using this WPF interface assures the programmer that there is a single secure source for state information and value resolution. Each command object keeps track of the associated event handler and manages its removal and the creation of a new handler when required. The property supports change notification and keeps track of the enabled state of the control. In this program, the sliders are only active if the richtextbox window/control is active. You are free to add custom code to the class methods. The class CommandSlider.cs is heavily documented and is worth studying if you are new to WPF. It is a bare bones, generic dependency class.

XML
<!--  This is a sample custom slider XAML statement.  --->

<local:CommandSlider x:Name="LeftMarginSlider" 
	Template="{StaticResource SliderTemplate}" HorizontalAlignment="Left"
    LargeChange="12" SmallChange="12" TickFrequency="12" Height="17" Width="386"
	Maximum="376" Cursor="Hand" Interval="12" Value="0" SnapsToDevicePixels="False"
	IsSnapToTickEnabled="True" IsMoveToPointEnabled="False" 
		AutoToolTipPlacement="BottomRight"
	TickPlacement="TopLeft" AutoToolTipPrecision="0"

<!-- These are the 3 dependency properties found at the beginning 
	of each custom command Class:   --->
<!--  This is the Command dependency entry  --->
Command="{x:Static local:rtbRuler.LeftMarginUpdtCmnd}"
<!--  CommandTarget dependency  --->
CommandTarget="{Binding ElementName=RichTextBox1}"
<!--  CommandParameter dependency  --->
CommandParameter="{Binding ElementName=LeftMarginSlider, Path=Value}"

	Focusable="False" VerticalAlignment="Bottom" Grid.RowSpan="2" >

<!-- This XAML Command Binding is located in the RichTextBox XAML code. --->
<!-- It connects the x:Name="LeftMarginSlider" XAML code above to the RichText Editor.--->
CommandBinding Command="{x:Static local:rtbRuler.LeftMarginUpdtCmnd}"
Executed="LeftSliderUpdateExecuted" CanExecute="SliderUpdatesCanExecute" 

Below is the Command class C# code in CommandSlider.cs for the 1st dependency in the XAML above. Each dependency property has similar C# code. The static dependency is created along with a property statement. Notice the use of GetValue and SetValue. These verbs do the actual work. The 'get' and 'set' verbs are basically just wrappers when using dependency properties.

C#
/// <summary>
/// Command: make a dependency property so it can be DataBound.
/// </summary>
public static readonly DependencyProperty CommandProperty =
            DependencyProperty.Register(
            "Command",                          // property name
            typeof(ICommand),                   // datatype
            typeof(CommandSlider),              // type that owns the property (slider)
            new PropertyMetadata((ICommand)null,         //  optional property settings
            new PropertyChangedCallback(CommandChanged)));//  optional callback for 
							// validation (see below)

public ICommand Command
{
    get {  return (ICommand)GetValue(CommandProperty);  }
    set {  SetValue(CommandProperty, value); }
}

The Font Dialog

I find it very frustrating dealing with dozens of fonts every time I want to select a font. I know with some certainty when I start a document what fonts and colors I am going to use. Moreover, if you are collaborating with others in a group, it’s always nice to have a standard font/color style that is shared by all contributors. The Font Dialog allows the user to select a subset of fonts & colors that are displayed in the combobox in the main richtext window toolbar.

The Font Dialog uses the drag-n-drop code developed by Josh Smith (and posted on this website). Josh’s technique uses observablecollections that store the fonts as objects (see colorselectionitem.cs in the code). When using this type of collection container, remember that the code updates the collection and not the listview. Each listview item is treated as single object entity and there is no item/sub-item breakdown.

This program stores any existing font/color combinations in the <appsettings>section of the App.Config in the following format:

XML
add key="Font2" value="Color=Navy,FontFamily=Book Antiqua" 

I use a regular expression to parse the setting value. The results are encapsulated into an object (see FontComboBoxItem.cs) and added to the FontStyleListView. The use of a regular expression is perhaps overkill but I want to expand this code in the future to add font decorators and I like the idea of just modifying the expression and adding the new variables to the match expression.

XML
<!--  This is the format for storing fonts and colors in the Apps.Config file.  --->
<configuration>
  	<appSettings>
    		<add key="xmlDataDirectory" value="c:\testdata"/>
    		<add key="Font1" value="Color=Brown,FontFamily=Arial" />
    		<add key="Font2" value="Color=Navy,FontFamily=Book Antiqua" />
    		<add key="Font3" value="Color=Black,FontFamily=Comic Sans MS" />
    		<add key="Font4" value="Color=DarkBlue,FontFamily=Courier New" />
    		<add key="Font5" value="Color=Navy,FontFamily=Times New Roman" />
    		<add key="Font6" value="Color=Black,FontFamily=Times New Roman" />
  	</appSettings>
</configuration>

This is the basic Regular Expression code used to parse the above appSettings values.

C#
Regex exprFonts = new Regex(@"^Color[\=]{1}(?<xColor>[\s\w]{3,24}),
	FontFamily[\=]{1}(?<xFont>[\s\w]{3,26})", RegexOptions.IgnoreCase);

string strAppSettings = ConfigurationManager.AppSettings[key];
strColor = "";
strFontFamily = "";

// Extract the FontFamily and Known Color from the AppSettings value parameter.
xmlElements = exprFonts.Matches(strAppSettings);
foreach (Match tempVar_xmlElem in xmlElements)
{
    strColor = tempVar_xmlElem.Groups[1].Value;
    strFontFamily = tempVar_xmlElem.Groups[2].Value;
}

The dialog box has a button that saves the contents of the FontStyleListView back to the App.Config File. The editor window always updates the toolbar FontComboBox from the values in App.Config. I want to modify the program in the future to give the user the option of saving the font/color information in the document itself. One last note: When you (re)build this project, the font settings in the App.Config file will overwrite any updates you have made to the config file that is paired with your binary. If you have font/color settings you want to keep – put them in the App.Config file before rebuilding the project.

The FontComboBox

The FontComboBox in the main window toolbar is updated from the App.Config settings whenever the Fonts dialog box is closed. In the richtext editor, the font at the current carat location is found in the combobox and displayed on the toolbar combobox. If the current font is not in the combobox, it is added and displayed. Any discovered fonts are added for display only and are not automatically added to the App.Config settings. These settings can only be updated in the Fonts dialog process.

There are some things about the background color in the combobox that I don't like but can't figure out how to fix. For example, I can easily change the ‘selected highlight’ background color in the dropdown portion of the combobox, but there doesn’t seem to be any way to change the ‘selected highlight’ background color in the toolbar text area of the combobox. It is currently a dark blue and seems determined to stay that way. Stay tuned.

To Do List

In addition to the items listed in the above text…

  1. There are a number of buttons at the bottom of the text in the richtextbox. They don't do anything right now because there is no way to protect the buttons from being deleted in the richtext editor. It seems they can be added but they can only be protected (used reliably) in a read only flow document environment. For this reason, I need to experiment with allowing the user to view the text as a read only flow document and then seamlessly switch a section of the document into a richtext editor with any richtext buttons disabled.
  2. I see no need to create a fancy WPF editor unless its main goal is to allow collaborative document creation. One of the biggest areas of potential for XAML (via Silverlight or WPF) lies in collaborative processing. I see the tools to write the code but I don't see a white paper that lays out an architecture for defining a new way to think about collaborative document creation and editing. We have an XAML definition but we don't yet have an XAML document protocol that defines a collaborative friendly meta-data to go with it. The world doesn’t need yet another richtext editor unless it adds something new and productive to the toolset.
  3. Is there some way to create an SQL stored procedure that supports on the fly creation of raw XAML documents from a relational database using simple keywords embedded in XAML<sections>? I like the idea of relational documents.

Credits

  • I made extensive use of Matthew MacDonald’s book “WPF in C# 2008”.
  • The Drag-n-Drop listview code is from an article by Josh Smith published on this web site. Josh’s copyright is clearly marked in the header of his code modules.
  • I made extensive use of MSDN code snippets.

History

  • 29th June, 2009: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Sh-Ya-Hung10-May-12 13:27
Sh-Ya-Hung10-May-12 13:27 
GeneralFind and Replace Method Pin
yuxiaofei28-Apr-10 16:29
yuxiaofei28-Apr-10 16:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.