Click here to Skip to main content
Click here to Skip to main content

A Bindable WPF RichTextBox

By , 13 Aug 2010
 
Screenshot

Introduction

WPF’s RichTextBox (RTB) is very good, but it suffers from several shortcomings:

  • It doesn't data-bind well, which makes it harder to use with the MVVM pattern; and
  • It outputs its content as a WPF FlowDocument, rather than as an XAML markup string.
  • It doesn't have a built-in formatting toolbar, which means additional work to set up the control in a host application.

As it turns out, the first two characteristics were not oversights, and they probably make a lot of sense. Even so, they are inconveniences, and it would be nice to have a version of the control that eliminates all of these problems. The control provided in this article does exactly that. It is included, along with a demo app, as both Visual Studio 2008 and Visual Studio 2010 RC solutions. Both solutions are included in the zip file at the top of this article.

Updates from Version 1.1

The current version of the control is Version 1.2; it incorporates the following changes from Version 1.1:

  • The List Numbering and List Bulleting buttons have been made toggle buttons and have been grouped together.
  • The text alignment buttons should be treated as a single-select button group--when one button is selected, the previously-selected button should be deselected. Version 1.1 did not implement this visual behavior; Version 1.2 does.
  • Version 1.2 adds two new text-deiting buttons, 'Format as code block', and 'Format as inline code'. These two buttons can be hidden by setting the CodeControlsVisibility visibility property to Visibility.Collapsed.
  • The source code is provided in WPF 4.0 format; I have dropped the WPF 3.5 version. If there is demand for a WPF 3.5 version, I'll consider backporting.

Why the WPF RichTextBox Behaves as It Does

The WPF RTB control outputs its content in its Document property. Unfortunately, this property is not a dependency property, which means that WPF won't data-bind to the property. As I noted above, this makes the control more difficult to use with the MVVM pattern, which has become the standard design pattern for WPF applications.

I have seen several explanations on the Web as to why the Document property isn't bindable. Now, I haven't seen anything from Microsoft’s WPF team, so the following is a bit speculative, but here is why I think the property isn't bindable: Like the WinForms RichTextBox, the WPF RichTextBox isn't really designed to be bound to a database. Instead, I suspect its designers intended it to be used more like a word processor, whose documents are loaded and saved to separate files. In that context, the lack of data binding is understandable.

Another reason to omit data binding from the control’s design has to do with processing load. One has to assume a rich text document can grow quite large. Any data bindings on the text should be updated whenever the text changes. That means whenever a character is typed. As a result, a data-bound RTB would be constantly updating the bindings, moving a large amount of formatted text as it does so. If the control is bound to a database, typing a character in the RTB could trigger a round trip to a database. Another understandable reason for making the RTB’s Document property non-bindable.

The Design of the FS RichTextBox

The FS RTB control is designed to make it easy to use an RTB in a data-bound view, while minimizing the processing load that comes with processing data-bound rich text. The control adds both a formatting toolbar and a Document dependency property to the WPF RTB. Since the Document property is a dependency property, the FS RTB can be data-bound to a view model, as is done in the demo app.

How does the control minimize the processing load associated with data-binding a rich text document? It does it by handling updates differently, depending on the direction of an update:

  • Updates coming from the view model update the RTB automatically. So, when an app loads a new document for display in the UI, it need only place that document in a view model property. The document will immediately appear in the RTB.
  • Updates coming from the RTB must be triggered by the host app. When the user enters text into the RTB, the controls Documents property is not updated until the host app calls the control’s UpdateDocumentBindings() method.

The host app determines when the Document property gets updates. It triggers the update by calling the UpdateDocumentBindings() method on the FS RTB. When that happens is entirely up to the host app. For example, it can use a LostFocus event handler to update the bindings whenever the FS RTB control loses focus. Or, it might trigger the update before it takes an action that would result in a loss of text in the control. For example, consider an app that loads a daily log entry into an RTB when a date is clicked on a calendar control. The app's date selection can call the UpdateDocumentBindings() method before it loads the new date’s text into the FS RTB.

Note that the FS RTB’s Document property is of type FlowDocument. At first glance, this appears to be a bad choice, since FlowDocuments are more difficult to work with than strings. Why not make the Document property of type String, and extract the XAML document markup from the FlowDocument as a string inside the control? It would certainly be easy enough to do. Here's why: Some developers may prefer to use binary serialization to persist the RTB text to a database, particularly for longer documents. In that case, the view model property to which the control is bound would probably be a binary type, rather than a string type.

But that doesn't mean that we are stuck with working with a FlowDocument in the host app. In the demo app, the FS RTB’s Document property is bound to a view mode string property called DocumentXaml. The demo uses a simple value converter to perform the conversion in both directions:

<fsc:FsRichTextBox ... Document="{Binding Path=DocumentXaml, Converter={StaticResource 
    flowDocumentConverter}, Mode=TwoWay}" ... />

The full implementation appears in MainWindow.xaml. Here is the code for the value converter:

using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Markup;

namespace FsRichTextBoxDemo
{
    [ValueConversion(typeof(string), typeof(FlowDocument))]
    public class FlowDocumentToXamlConverter : IValueConverter
    {
        #region IValueConverter Members

        /// <summary>
        /// Converts from XAML markup to a WPF FlowDocument.
        /// </summary>
        public object Convert(object value, System.Type targetType, 
		object parameter, System.Globalization.CultureInfo culture)
        {
            /* See http://stackoverflow.com/questions/897505/
		getting-a-flowdocument-from-a-xaml-template-file */

            var flowDocument = new FlowDocument();
            if (value != null)
            {
                var xamlText = (string) value;
                flowDocument = (FlowDocument)XamlReader.Parse(xamlText); 
            }

            // Set return value
            return flowDocument; 
        }

        /// <summary>
        /// Converts from a WPF FlowDocument to a XAML markup string.
        /// </summary>
        public object ConvertBack(object value, System.Type targetType, 
		object parameter, System.Globalization.CultureInfo culture)
        {
            /* This converter does not insert returns or indentation into the XAML. 
             * If you need to indent the XAML in a text box, 
             * see http://www.knowdotnet.com/articles/indentxml.html */

            // Exit if FlowDocument is null
            if (value == null) return string.Empty;

            // Get flow document from value passed in
            var flowDocument = (FlowDocument)value;

            // Convert to XAML and return
            return XamlWriter.Save(flowDocument);
        }

        #endregion
    }
}

Value conversion provides a more flexible solution that allows a developer to bind the FS RTB to many different property types in a view model.

Demo Walkthrough

The demo app has a single window with four controls; an FS RTB, two buttons, and a gray text box. The FS RTB is discussed above. The two buttons simulate a host app taking a couple of different actions, and the text box shows the XAML markup generated by those actions.

The RTB and the text box are both bound to the DocumentXaml property in MainWindowViewModel.cs. Here is the RTB declaration:

<fsc:FsRichTextBox x:Name="EditBox" Document="{Binding Path=DocumentXaml, 
    Converter={StaticResource flowDocumentConverter}, Mode=TwoWay}" Grid.Row="0" 
    Margin="10,10,10,5" />

And here is the text box declaration:

<TextBox Text="{Binding DocumentXaml}" Margin="10,5,10,10" Grid.Row="2" 
	Background="Gainsboro" 
    	TextWrapping="Wrap">

As we note above, the RTB uses a value converter, FlowDocumentToXamlConverter, to perform the conversion between the Document property on the FS RTB control and the DocumentXaml property on the view model. Since the text box is also bound to the DocumentXaml property, the text box will update in response to any property updates.

When the demo starts, the RTB and text box will be empty. As a first step, type some text into the RTB. Note that the text box remains empty. That’s because the text in the RTB hasn't yet been pushed out to the FS RTB’s Document property. Remember, the property gets updated only when the host app invokes the UpdateDocumentBindings() method.

Now click the ForceUpdate button. This button invokes the UpdateDocumentBindings() method, the same way an app would before taking an action that would result in a loss of text in the RTB. An XAML representation of the text in the RTB immediately appears in the text box. What happened is that the UpdateDocumentBindings() method pushed the RTB’s text out to the FS RTB’s Document property, which is data-bound to the view model’s DocumentXaml property. When the DocumentXaml property got updated, the change flowed back to the text box in the UI, since it is bound to that property, as well.

Finally, click the Load Document button. This button simulates the host app loading a rich text document from a data store. In the demo app, the button is bound to an ICommand in the view model that simply sets the view model’s DocumentXaml property with some hard-coded XAML. However, the effect is the same as if the command had gotten the XAML from a data store and then set the property.

When you click the Load Document button, the hard-coded text immediately appears in both the RTB and the text box, since both are bound to the view model’s DocumentXaml property. The point is that changes to a view model property bound to the FS RTB’s Document property are automatically pushed through to the RTB--no host app triggering is required. In short, changes from the UI to the view model need to be triggered by the host app, but changes from the view model to the UI are automatic.

How the Control Works

The FsRichTextBox control itself is pretty straightforward. It is a user control with two constituent controls; a WPF RichTextBox control and a formatting toolbar. The formatting buttons are wired to commands from the WPF command library.

The toolbar itself merits a comment. I decided against using a WPF toolbar, because it carries a lot of visual and logical overhead to support features like dragability and overflow buttons. These features have no meaning in the context of this particular toolbar, so I used a StackPanel to emulate a toolbar. The primary disadvantages of this approach are that buttons lose their ‘toolbar look’ (they appear as standard silver buttons in a StackPanel), and the ‘toolbar’ loses the tag that the WPF toolbar uses to create separators.

The control restores the toolbar look to the formatting buttons by creating a simple button control template that styles the formatting buttons to look like toolbar buttons. The control template is located in the section of the user control XAML:

<ControlTemplate x:Key="FlatButtonControlTemplate" TargetType="{x:Type Button}">
    <Border x:Name="OuterBorder" BorderBrush="Transparent" 
	BorderThickness="1" CornerRadius="2">
        <Border x:Name="InnerBorder" Background="Transparent" 
	BorderBrush="Transparent" BorderThickness="1" CornerRadius="2">
            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" 
		RecognizesAccessKey="True" Margin="{TemplateBinding Padding}" />
        </Border>
    </Border>
    <ControlTemplate.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter TargetName="OuterBorder" Property="BorderBrush" Value="#FF7CA0CC" />
            <Setter TargetName="InnerBorder" Property="BorderBrush" Value="#FFE4EFFD" />
            <Setter TargetName="InnerBorder" Property="Background" Value="#FFDAE7F5" />
        </Trigger>
        <Trigger Property="IsPressed" Value="True">
            <Setter TargetName="OuterBorder" Property="BorderBrush" Value="#FF2E4E76" />
            <Setter TargetName="InnerBorder" Property="BorderBrush" Value="#FF116EE4" />
            <Setter TargetName="InnerBorder" Property="Background" Value="#FF3272B8" />
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

The control restores the separator feature with a simple image that reproduces the appearance of a separator. The result is a lightweight toolbar that does only what it needs to do.

As is discussed above, the host app forces an update to the FS RTB’s Document property by calling the control’s UpdateDocumentBindings() method. That method reads as follows:

/// <summary>
/// Forces an update of the Document property.
/// </summary>
public void UpdateDocumentBindings()
{
    // Exit if text hasn't changed
    if (!m_TextHasChanged) return;

    // Set 'Internal Update Pending' flag
    m_InternalUpdatePending = 2;

    // Set Document property
    SetValue(DocumentProperty, this.TextBox.Document); 
}

The method first checks to see if the text in the RTB has actually changed. If all the user has done is view the text, we don't need to update the property, and we can avoid a round-trip to the database. Since the control performs this check, the host app can call the method whenever an action would result in the loss of edited text, without worrying whether the user has actually edited the text or not. Next, the method sets an InternalUpdatePending flag, which is discussed below. Finally, the method copies the contents of the WPF RTB to the FS RTB control’s Document property. From there, WPF data-binding takes over.

The heart of the FS RTB control is the Document dependency property added to FsRichTextBox.xaml.cs:

// Document property
public static readonly DependencyProperty DocumentProperty = 
    DependencyProperty.Register("Document", typeof(FlowDocument), 
    typeof(FsRichTextBox), new PropertyMetadata(OnDocumentChanged));

The Document property utilizes a PropertyChanged callback method, OnDocumentChanged(). This method determines whether the property change is coming from the control (because the app has triggered a bindings update), or from the view model. If the change is coming from the view model, the method passes the change through to the WPF RTB in the control. However, if the change is coming from the control, the method does nothing—remember, the property has already been changed.

The OnDocumentChanged() method uses the InternalUpdatePending flag to determine the origin of the change. Recall that the flag was set by the UpdateDocumentBindings() method when the host app (or the user) triggered an update. Note that the flag is an integer, rather than a Boolean—more on that in a minute. The OnDocumentChanged() method checks this flag and, if it is set, does nothing, other than decrementing the flag.

/// <summary>
/// Called when the Document property is changed
/// </summary>
private static void OnDocumentChanged
	(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    /* For unknown reasons, this method gets called twice when the 
     * Document property is set. Until I figure out why, I initialize
     * the flag to 2 and decrement it each time through this method. */

    // Initialize
    var thisControl = (FsRichTextBox)d;

    // Exit if this update was internally generated
    if (thisControl.m_InternalUpdatePending > 0)
    {

        // Decrement flag and exit
        thisControl.m_InternalUpdatePending--;
        return;
    }

    // Set Document property on RichTextBox
    thisControl.TextBox.Document = (e.NewValue == null)? 
        new FlowDocument(): 
        (FlowDocument)e.NewValue;

    // Reset the TextChanged flag
    thisControl.m_TextHasChanged = false;
}

There is an anomaly concerning the OnDocumentChanged() method. For some reason, the method is being called twice when the FS RTB’s Document property changes. Quite frankly, I haven't been able to determine why this is happening, and so I have resorted to hacking my way around the problem. The InternalUpdatePending flag is created as an integer variable, rather than a Boolean, and is instantiated to a value of 2. On each pass through the OnDocumentChanged() method, the flag’s value is decremented, with the result that it causes the control to do nothing on both passes through the method, and it is cleared on the last pass through.

If a reader can tell me why the OnDocumentChanged() method is getting called twice, I'd be most appreciative. I'll replace the hack with a proper Boolean flag and will be happy to credit your contribution in an article update. In the meantime, the hack doesn't affect either the performance or the output of the control. It’s ugly, but it works.

MVVM in the Demo App

The demo app uses the MVVM pattern, so that you can see how the FS RTB control fits within MVVM. Part of the strength of the MVVM pattern is its flexibility; developers can implement the pattern in a number of ways, all of which may be regarded as good practice. With that in mind, a word or two on my implementation of MVVM may make the demo app easier to figure out. But you can easily skip this section, unless you are having a problem figuring out how I structured the demo app.

image002.gif

I use a variation of the View-first approach to MVVM. I implement a view model using multiple classes. The main class is, of course, the ViewModel class, which in the demo app is MainWindowViewModel.cs. I set this class as the DataContext of the view (MainWindow.xaml in the demo) in a third class that instantiates both the View and the ViewModel classes. In the demo app, I perform this step in App.xaml.cs, by overriding the OnStartup() method. Note that I also remove the StartupUri property from the < Application> tag in App.xaml.

So, in my implementation, neither the View nor the ViewModel instantiates the other. I use this third-class approach to loosen the couplings between the View and the ViewModel classes.

There will invariably be some dependencies between the View and the ViewModel, and I generally maintain these dependencies so that they run from the View to the ViewModel. In other words, the View knows about the ViewModel, but not vice-versa. Running the dependencies in the other direction, as is done in the PresentationModel pattern, is of course good practice; this is simply my preferred style of MVVM. In any event, the direction of the dependencies has little if any impact in the demo app.

I bind buttons and other command controls in the view to ICommand properties in the ViewModel class. These properties are bound to ICommand classes that I store in my ViewModel folder. The Execute() methods of my commands take simple actions themselves and delegate more complex actions to service classes in the business layer of the application. In the demo app, there is one command, LoadDocument. Its Execute() action is simple, so it implements it directly.

To keep things simple, I wired the Force Update button directly to an event handler in the MainWindow code-behind. That’s not good MVVM practice, and I wouldn't do that in a production app. Since this is a simple demo, I don't think it really matters.

If you are just learning MVVM, you might find my article, MVVM and the WPF DataGrid, helpful. I explain the MVVM approach I use and how I use it to structure a simple application.

Conclusion

As always, I appreciate the comments of the folks who read these articles. I hope you find the FS RTB useful. I plan to update this article from time-to-time to incorporate any suggestions made by readers, and I will, of course, provide credit in the update for those suggestions.

History

  • 16th March, 2010: Initial version
  • 17th March, 2010: Added VS2008 version of the code
  • 12th August, 2010: Article updated

License

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

About the Author

David Veeneman
Software Developer (Senior) Foresight Systems
United States United States
Member
David Veeneman is a financial planner and software developer. He is the author of "The Fortune in Your Future" (McGraw-Hill 1998). His company, Foresight Systems, develops planning and financial software.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionVisibilitymemberdknabben13 Mar '13 - 2:26 
Is it possible to set the toolbar visiblity to false? I have two different user groups in my app.One group is only able to view text and one group is also able to edit text. I want to use the same control for both groups!
 
Thank you for your answer.
GeneralMy Vote of 5 [modified]memberNigel Shaw14 Dec '12 - 7:01 
You got my vote of 5 though! Thanks a lot.
Nigel


modified 14 Dec '12 - 16:42.

BugFIX FOR: Binding update target to source doesn't seem to be firing [modified]memberNigel Shaw14 Dec '12 - 7:01 
As natalie74 points out below, the binding from target to source to update the source when the textbox loses focus doesn't seem to be working.
 
I looked into it and here's the fix. Add the following line to the OnTextChanged method:
 
Document = TextBox.Document; 
 
So the full method is;
 
/// <summary>
///  Invoked when the user changes text in this user control.
/// </summary>
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
    // Set the TextChanged flag
    m_TextHasChanged = true;
 
    // Nigel Shaw 14/12/2012.
    Document = TextBox.Document;
}
Nigel


modified 14 Dec '12 - 22:47.

BugError in source zip filememberKamran Behzad10 Dec '12 - 13:28 
There seems to be an update on this article just yesterday. I tried to use the source but there is an error in the zip file. Can you please double-check. I am looking forward to giving this a try.
BugProblem with image in figurememberKonrad Perzyna26 Sep '12 - 10:45 
Hi!
Great control. It saved me a lot of time on figuring out how to do that, and then actually doing it. Thanks.
I found a little bug. I got images in figures in my flowdocs.
When I select (or try to drag) the image NullReferenceException is thrown.
 
It turned out that the Text of TextRegion in such case is " ", so it's neither null nor empty and as "textRange.GetPropertyValue(Inline.TextDecorationsProperty)" is null then, exception is thrown.
 
The solution is to add Trim() to the condition in line 363 in SetToolbar() method:
// Set Font buttons
if (!String.IsNullOrEmpty(textRange.Text.Trim()))

GeneralWow!!!membercool_leahcim13 Sep '12 - 16:28 
Thank you very much for this excellent example. Working great!!! Big Grin | :-D
GeneralMy vote of 5memberVitaly Tomilov10 Jun '12 - 10:04 
Great Article!
QuestionStyleProperty [modified]memberSesharaman22 May '12 - 6:36 
The article penned by you is excellent. I am a newbie to wpf. You have put great efforts into it to fructify the result.
My request to you is:
I am using styles on paragraphs of the flowdocument like content. When used in the richtextbox after edit, the styles are lost. Can you suggest ways and means to go about it. If you are going to give me a piece of code there is nothing like it. But for short of time, if you are going to give me a small write up to go about it, fine.
Thank you.
I am doing work on Sanskrit Language and giving the content for free to others.
V.Sesharaman
 
TextBox.CaretPosition.GetTextInRun(LogicalDirection.Backward)
gave me the full text of the paragraph, by first locating GetNextContextPosition and then giving a buffer with large number of bytes(3300) and getting the paragraph full text. My attempts at getting the style applied already failed to yield anything. So I request you may kindly see for getting the style and then reapplying it with a different one.
If it is not possible at all through code. Kindly convey me.

modified 26 May '12 - 1:29.

GeneralMy vote of 5membermanoj kumar choubey23 Mar '12 - 1:33 
nice
GeneralMy vote of 5memberJamesWittHurst18 Feb '12 - 19:34 
I like your work - clean API, nicely structured code, UI is uncluttered but useful - and your code builds without problem (on my box). Thank you for sharing! jh
QuestionThanks tomemberMember 83938422 Feb '12 - 14:42 
Thank you very much for this guide!
You saved my life!!! Smile | :)
QuestionSilverlight version?memberPepLamb25 Jun '11 - 22:29 
Great post will you have a Silverlight version of it?
Thanks!
QuestionSilverlight version?memberzzaamine25 Jun '11 - 22:23 
will you also release a silverlight version?
GeneralSmall improvement [modified]memberdehpch17 May '11 - 5:19 
Great user control. Exactly what I needed. OH! And it translated to VB.NET very cleanly.
 
I noticed that when I select some text, then click an edit button, the selected text highlight disappears in the text box. So I did a little searching and found that this is the nature of the standard RichTextBox, and has simple fix. Just set the Routed Event's Handled property to True in the RichTextBox's LostFocus event handler:
 
VB:
Private Sub TextBox_LostFocus(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles TextBox.LostFocus
	If Not TextBox.Selection.IsEmpty Then
		e.Handled = True
	End If
End Sub
 
C#:
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
	if (TextBox.Selection.IsEmpty == false)
	{
		e.Handled = true;
	}
}
 
I also added an Enumeration and a Dependency Property to it:
Public Enum ButtonSets
	Basic
	Simple
	Advanced
End Enum
 
Public Shared ReadOnly ButtonSetProperty As DependencyProperty = _
  DependencyProperty.Register("ButtonSet", GetType(ButtonSets), GetType(FsRichTextBox), _
  New FrameworkPropertyMetadata(ButtonSets.Advanced))
 
Public Property ButtonSet() As ButtonSets
	Get
		Return CType(GetValue(ButtonSetProperty), ButtonSets)
	End Get
	Set(ByVal value As ButtonSets)
		SetValue(ButtonSetProperty, value)
	End Set
End Property
 
I changed the StackPanel in the original code to a WrapPanel. Then I set each set of buttons, with their separators, in a separate StackPanel inside the WrapPanel. By settings Styles and DataTriggers on these StackPanels, I can show all or only some of the edit buttons, depending on how much editing ability I want for a particular application.
 
<Style x:Key="HideWhenBasic" TargetType="{x:Type StackPanel}">
	<Setter Property="Visibility" Value="Visible" />
	<Style.Triggers>
		<DataTrigger Binding="{Binding ElementName=FsRichTextBoxControl, Path=ButtonSet}" Value="Basic">
			<Setter Property="Visibility" Value="Collapsed" />
		</DataTrigger>
	</Style.Triggers>
</Style>
...
...
<WrapPanel Orientation="Horizontal" Name="wpToolBar" Background="{Binding ElementName=FsRichTextBox, Path=ToolbarBackground}">
	<StackPanel Orientation="Horizontal" Style="{StaticResource HideWhenBasicOrSimple}"> ... Buttons ... </StackPanel>
	<StackPanel Orientation="Horizontal" Style="{StaticResource HideWhenBasic}"> ... Separator Buttons ... </StackPanel>
	<StackPanel Orientation="Horizontal" Style="{StaticResource HideWhenBasic}"> ... Separator Buttons ... </StackPanel>
</WrapPanel>
Please feel free to use these changes. Glad to contribute.
modified on Tuesday, May 17, 2011 11:49 AM

GeneralRe: Small improvementmemberDavid Veeneman17 May '11 - 5:29 
Nice improvements--thanks for posting!
David Veeneman
www.veeneman.com

GeneralRe: Small improvement [modified]memberNigel Shaw14 Dec '12 - 6:52 
Nice improvements. Thanks for the highlight tip. I'll remember that!
Nigel

GeneralEasier way to get indented XAML outputmemberrivalschool1 Mar '11 - 8:02 
        public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            /* This converter does not insert returns or indentation into the XAML. If you need to 
             * indent the XAML in a text box, see http://www.knowdotnet.com/articles/indentxml.html */
 
            // Exit if FlowDocument is null
            if (value == null) return string.Empty;
 
            // Get flow document from value passed in
            var flowDocument = (FlowDocument)value;
 
            // Convert to XAML and return

            return XElement.Parse(XamlWriter.Save(flowDocument)).ToString();
        }
 
Great article,
 
RS
GeneralWhere is the text changed eventmemberDave Midgley26 Feb '11 - 11:36 
I'm a newbie as far as XAML and WPF is concerned, so this may be a stupid question, but I'd like to know what happened to the TextChanged event. Most or all of the other events exposed by RichTextBox are also exposed by FSRichTextBox, but TextChanged seems not to be, and I'd really like to have it. I've delved into the FSRichTextBox code but I can't actually work out how the other events are exposed, so I definitely can't work out how to expose this missing one.
Can anyone explain this?
Dave

QuestionConvert to Silverlight?memberjackmos27 Nov '10 - 4:59 
Will you be converting this great control to Silverlight?
I sure can use it.
GeneralMy vote of 5memberjwhurst3 Oct '10 - 7:06 
Very well explained; excellent learning article useful sample code. Thanks for sharing.
QuestionImagesmemberledpup21 Sep '10 - 21:43 
This is a great control. I'd like to be able to include images in my controls, however. At the moment, if I do that, I get a "The URI prefix is not recognized." error when I re-load the Xaml. Reading http://stackoverflow.com/questions/343468/richtextbox-wpf-binding[^] suggests I may need to switch it to the XamlPackage format. Does that sound like the correct thing to do? Any idea how to do it?
 
Thanks
AnswerRe: Imagesmemberledpup22 Sep '10 - 15:51 
I converted the FlowDocument to Xaml converter to convert between FlowDocument and XamlPackage. Now I can save/load rich text with images. Nice.
 
    [ValueConversion(typeof(string), typeof(FlowDocument))]
    public class FlowDocumentToXamlConverter : IValueConverter
    {
        /// <summary>
        /// Converts from XamlPackage to a WPF FlowDocument.
        /// </summary>
        public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            var flowDocument = new FlowDocument();
 
            if (value != null)
            {
                var stream = new MemoryStream((byte[])value);
 
                var range = new TextRange(flowDocument.ContentStart, flowDocument.ContentEnd);
                range.Load(stream, DataFormats.XamlPackage);
            }
 
            return flowDocument;
        }
 
        /// <summary>
        /// Converts from a WPF FlowDocument to a XamlPackage.
        /// </summary>
        public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value == null) 
                return null;
 
            var flowDocument = (FlowDocument)value;
 
            var stream = new MemoryStream();
 
            var range = new TextRange(flowDocument.ContentStart, flowDocument.ContentEnd);
            range.Save(stream, DataFormats.XamlPackage);
 
            var binaryReader = new BinaryReader(stream);
 
            return stream.ToArray();
        }
    }

GeneralRe: ImagesmemberDavid Veeneman22 Sep '10 - 16:03 
Now, that's pretty cool. Thanks for sharing!
David Veeneman
www.veeneman.com

GeneralMy vote of 5memberateista16 Sep '10 - 6:50 
Amazing work, simple and clean. Thats the first bindable RichTextBox that really works that I found (after testing 3 others)
Thank you.
(Sorry for my bad engish)
GeneralI use your FsRichTextBox UserControl in the DataTemplate of a DataGrid`s Cell AND I gave you a 5 :-) [modified]membernatalie7418 Aug '10 - 9:29 
Your approach works nice if you use your RichTextBox UserControl as single editor control.
 
I do not think my scenario is very unusual, but its damn hard to implement MVVM here even half-hearted. At the moment I have really problems getting the data out of the DataGrid`s cell to my database... because I am not able to do something like this:
 
this.EditBox.UpdateDocumentBindings();
 
ah ok here we go Wink | ;-)
 
I have a DataGrid with some DataGridTemplateColumn`s like this: http://pastebin.com/mHeJC7Et (I posted it on pastebin because codeproject code formatting makes all unreadable with much code text)
 
1. My UserControl has the Name MyRTFBox but I can not call this from anywhere ?
2. Lets assume I do not need that Name. I just subscribed a LostFocus event on the code like this:
 
<View:FsRichTextBox x:Name="MyRTFBox" LostFocus="MyRTFBox_LostFocus" Document="{Binding Path=ToDo,Converter={StaticResource flowDocumentConverter}, Mode=TwoWay}"/> 
 
In codebehind I react on the MyRTFBox_LostFocus event:
private void MyRTFBox_LostFocus(object sender, RoutedEventArgs e)
{ 
   // tried to get via e.Source the RichTextBox in the DataGrid`s cell, got luck but its the wrong cell? I get data of the cell where I clicked into when    //I lost the focus 
}
 
Do you have an idea how to make that scenario working without starting a brute force hack?
 
UPDATE: I just found out, that when I click into another DataGrid cell and the LostFocus is fired the code SetValue is not executed. I mean in debug mode SetValue seems called but nothing happens. Normally I should see now the bound property to the Document property changig its value in the setter just like it is happening in your sample. I get no binding error in the output window :/
 
public void UpdateDocumentBindings()
{...
            // Set Document property
            SetValue(DocumentProperty, this.TextBox.Document); // after... the code never goes into public object ConvertBack(..) method ? The debugger just passes by the SetValue method. My property bound to Document is hit 4 times on app startup because I bind a list with 4 objects to the DataGrid so it can not be my properties failure. Question is why is SetValue not executed?
}
 
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
    UpdateDocumentBindings();  
}
 
cheerio,
 
Natalie
 

modified on Wednesday, August 18, 2010 4:23 PM

GeneralProperty to Hide the ToolbarmemberBurkeJones15 Jul '10 - 4:28 
I appologize if it already exists and I missed it, but I think a great addition to your control would be to implement a property to hide the tool bar. This would allow you to use the bindable RichTextBox in a ListView without the editing controls. For example:
<ListView>
         <ListView.ItemTemplate>
             <DataTemplate>
                 <fs:FsRichTextBox Document="{Binding Path=yourProperty, Converter={StaticResource flowDocumentConverter}, Mode=TwoWay}" ToolBarVisibility="Hidden" />
             </DataTemplate>
         </ListView.ItemTemplate>
</ListView>
 
I am working on a solution for this, but would greatly welcome any input you might have.
GeneralRe: Property to Hide the ToolbarmemberBurkeJones15 Jul '10 - 4:54 
I just wanted to let anyone who is looking for it know that this was easier than I thought to implement. Here's the code changes:
add to FsRichTextBox.cs:
// ToolbarVisibility
        public static readonly DependencyProperty ToolbarVisibilityProperty =
            DependencyProperty.Register("ToolbarVisibility", typeof(Visibility),
            typeof(FsRichTextBox));
 
/// <summary>
        /// The visibility of the formatting toolbar.
        /// </summary>
        [Browsable(true)]
        [Category("Other")]
        [Description("The visibility of the formatting toolbar.")]
        [DefaultValue(Visibility.Visible)]
        public Visibility ToolbarVisibility
        {
            get { return (Visibility)GetValue(ToolbarVisibilityProperty); }
            set { SetValue(ToolbarVisibilityProperty, value); }
        }
 
And Edit about line 40 of FsRichTextBox.xaml to add the Visibility line shown below to the Border control:
...
 
 SnapsToDevicePixels="True"
            Visibility="{Binding ElementName=FsRichTextBoxControl, Path=ToolbarVisibility}">
            <StackPanel Orientation="Horizontal" Height="24" Background="{Binding ElementName=FsRichTextBoxControl, Path=ToolbarBackground}" >
 
...
 
Awesome control David. Taught me quite a bit about WPF.
GeneralRe: Property to Hide the ToolbarmemberDavid Veeneman15 Jul '10 - 5:47 
Thanks Burke, and thanks for your contribution!
David Veeneman
www.veeneman.com

GeneralSuggestionsmemberJab444 Jul '10 - 12:59 
Thanks, Great works.
Suggestions :
Add image with adorner on it (crop,resize)
Add video with adorner on it (play, stop,...)
Spelling...
and you'll obtain a multimedia editor.
Best Regards
GeneralThanks.. Great Workmemberbhuvan0128 Jun '10 - 5:47 
Hi David..
 
Great work.
 
There is another similar article by <b>Michael Synch's</b> <a href="http://www.codeproject.com/KB/WPF/wpf-richtexteditor.aspx">WPF RichText Editor</a>
It would be great if you specify the advantages of your approach over that article.
 
thanks in advance.
 
bp
GeneralVS 2008 Version bug with m_InternalUpdatePendingmemberdagware11 May '10 - 5:34 
First off, GREAT solution!! Thanks a million for doing this.
 
I compiled and ran your VS 2008 version, and there's a bug where clicking "Load Document" doesn't work (and I believe I have the solution, below). Here's the steps to reproduce the bug:
 
1) Run the VS 2008 Demo App.
2) Click "Load Document".
3) Type some additional text in the box.
4) Click "Force Update".
5) Click "Load Document". The text the RichTextBox doesn't change.
 
If I comment out the lines in OnDocumentChanged that refer to "m_InternalUpdatePending", then the code works fine.
 
Since I don't have 2010 yet, I can't test against that. But something is obviously different.
 
Thought you'd like to know. Thanks again!
 
Dan
QuestionHow to call UpdateDocumentBindings()memberDavid Veeneman21 Mar '10 - 4:52 
I the next revision of the article I will add some text on how to call UpdateDocumentBindings(). In the interim, here is how I do it: My view model base class implements both INotifyPropertyChanged (which is standard for MVVM) and INotifyPropertyChanging. The base class contains methods to raise both events, which are called from the setters of my view model properties:
 
// A bound property on my view model
public Note SelectedNote
{
    get { return p_SelectedNote; }
 
    set
    {
        base.RaisePropertyChangingEvent("SelectedNote");
        p_SelectedNote = value;
        base.RaisePropertyChangedEvent("SelectedNote");
    }
}
 
The view (i.e., the WPF window) subscribes to the PropertyChanging event, so it gets notification before a view model property is changed. So, if I change the Note selection in a list box, the view will be notified before the Note property is changed to a different object. The view handles the event in code-behind like this:
 
// PropertyChanging event handler in the View
void OnViewModelPropertyChanging(object sender, System.ComponentModel.PropertyChangingEventArgs e)
{
    // Get view model
    var viewModel = (MainWindowViewModel)this.DataContext;
 
    // Stop property change events
    viewModel.IgnorePropertyChangeEvents = true;
 
    // Update appropriate bindings
    switch(e.PropertyName)
    {
        case "SelectedTopic":
            this.DiscussionBox.UpdateDocumentBindings();
            break;
 
        case "SelectedNote":
            this.NoteBox.UpdateDocumentBindings();
            break;
    }
 
    // Restart property change events
    viewModel.IgnorePropertyChangeEvents = false;
}
 
IgnorePropertyChangeEvents is a property in my view model base class. If this property is set to true, the methods that raise property change events exit without raising their events. This prevents a cascading event when the FsRichTextBox.Document property is set.
 
Here is the complete code to the view model base class:
 
using System.ComponentModel;
 
namespace MyApplicationName.ViewModel.BaseClasses
{
    public abstract class ViewModelBase : INotifyPropertyChanging, INotifyPropertyChanged
    {
        #region INotifyPropertyChanging Members
 
        public event PropertyChangingEventHandler PropertyChanging;
 
        #endregion
 
        #region INotifyPropertyChanged Members
 
        public event PropertyChangedEventHandler PropertyChanged;
 
        #endregion
 
        #region Control Properties
 
        /// <summary>
        /// Whether the view model should ignore property-change events.
        /// </summary>
        public bool IgnorePropertyChangeEvents { get; set; }
 
        #endregion
 
        #region Protected Methods
 
        /// <summary>
        /// Raises the PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">The name of the changed property.</param>
        protected void RaisePropertyChangedEvent(string propertyName)
        {
            // Exit if changes ignored
            if (this.IgnorePropertyChangeEvents) return;
 
            // Exit if no subscribers
            if (PropertyChanged == null) return;
 
            // Raise event
            var e = new PropertyChangedEventArgs(propertyName);
            PropertyChanged(this, e);
        }
 
        /// <summary>
        /// Raises the PropertyChanging event.
        /// </summary>
        /// <param name="propertyName">The name of the changing property.</param>
        protected void RaisePropertyChangingEvent(string propertyName)
        {
            // Exit if changes ignored
            if (this.IgnorePropertyChangeEvents) return;
 
            // Exit if no subscribers
            if (PropertyChanging == null) return;
 
            // Raise event
            var e = new PropertyChangingEventArgs(propertyName);
            PropertyChanging(this, e);
        }
 
        #endregion
    }
}
David Veeneman
www.veeneman.com

GeneralArticle updatedmemberDavid Veeneman20 Mar '10 - 7:05 
I have updated this article and its source code, based on feedback that I have received. Changes are described at the top of the article.
David Veeneman
www.veeneman.com

GeneralVS 2008 Version Included in Source Code FilememberDavid Veeneman17 Mar '10 - 10:10 
I have added a VS 2008 (.NET 3.5) version of the control and the demo in the source code zip file for this project. The source code zip now contains both VS 2008 and VS 2010 versions. I will update the article text to reflect that fact with the next update.
David Veeneman
www.veeneman.com

GeneralRe: VS 2008 Version Included in Source Code FilememberYudhaPH18 Oct '11 - 2:31 
Yes, on the 'Browse Code' tab above contained VS 2008 project version. But I cannot find them in the zip file coming from this article download link. Am I doing wrong?
 
Please update the download link above. I only have VS 2008 on my PC. Thanks.
GeneralNicemvpSacha Barber16 Mar '10 - 23:45 
I thought this was getting fixed in WPF 4.0. Have you also seen this article, its cool as well
 
http://michaelsync.net/2009/06/09/bindable-wpf-richtext-editor-with-xamlhtml-convertor[^]
Sacha Barber
  • Microsoft Visual C# MVP 2008/2009
  • Codeproject MVP 2008/2009
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralGreat controlmemberMarcelo Ricardo de Oliveira16 Mar '10 - 17:51 
Yes, unfortunately WPF RichTextBox lacks the binding capabilities, although FlowDocument was an interesting idea. But fortunately WPF provides powerful binding capabilities that allow us to extend the existing controls like you did.
 
Pretty useful control and great article, David, got 5 stars from me Thumbs Up | :thumbsup:
 
cheers,
marcelo
Take a look at XNA Snooker Club game here in Code Project.

GeneralFramework Versionmembertonyt16 Mar '10 - 13:30 
I thought it would be fair to ask this before casting a vote.
 
Is there a dependence on .NET 4.0 that wasn't mentioned?
GeneralRe: Framework VersionmemberDavid Veeneman16 Mar '10 - 14:06 
No .NET 4.0 dependencies; the code was simply written and compiled in VS 2010. The code can be recompiled under .NET 3.5 in VS 2010, or imported into a VS 2008 solution and compiled there. The control doesn't depend on any .NET 4.0 features.
David Veeneman
www.veeneman.com

GeneralRe: Framework Versionmembersam.hill16 Mar '10 - 14:38 
This is a pain getting converted for use with VS8.
Can you repost as a native VS8 solution?
Thanks
GeneralRe: Framework VersionmemberDavid Veeneman16 Mar '10 - 15:09 
Sure--I'll add a VS 2008 version in the next few days. I'll post a message when its uploaded.
David Veeneman
www.veeneman.com

GeneralRe: Framework Versionmembersam.hill16 Mar '10 - 17:20 
Thanks very much.

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 13 Aug 2010
Article Copyright 2010 by David Veeneman
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid