Bindable WPF RichText Editor with XAML/HTML Convertor 32

Introduction

This post will give you some tips/tricks of using Rich Textbox in WPF. As we all know, the build-in WPF RichTexbox doesn’t provide some features that we are looking for so if you are in need of using RichTexbox in WPF project, you should know that you will need to roll your own implementation (at least) a bit. In this post, I will brief you how to make WPF RichTextbox bindable, how to display the HTML in WPF, how to create a Rich Textbox Editor with toolbar.

Contents

  • Bindable RichTextbox
  • RichText Editor
  • HTML to XAML Conversion

wpf-rich-text-editor

Source DownloadWpfRichText.Ex.zip (336 KB)

Dependencies ~

Bindable Rich Textbox

A lot of people asked how to bind RichTextbox on the net. Yes. IMO, the Rich Textbox should be bindable but I’m not sure why Document property of RichText is not a dependency property in WPF ( someone can ask me this question?) but people like us who are using MVVM pattern need to have a binding between RichTextbox and the property of ViewModel.  How are we going to make this happen?

Well,  we probably need to a custom property that can be bindable in that control so the first thing that come into my mind is the attached property. There maybe a lot of definitations for it but the way I understand is that it is a custom property that can be attached to control. For example: AA property to B Control or etc.

You can take a look at how Sam implemented the binding support for Passwordbox in his post. (Forget about encrypting the password in memory or etc for now. ) We will follow this approach to implement the binding support in RichTextbox as well.

The first thing that you might notice is that RichTextbox has the Document property. So, you can create an attached property by wrapping RichTextbox.Document property. Please take a look at siz‘s implementation as below (link: ref).


class RichTextboxAssistant : DependencyObject
{
public static readonly DependencyProperty DocumentProperty =
DependencyProperty.Register("Document",
typeof(FlowDocument),
typeof(RichTextboxAssistant),
new PropertyMetadata(new PropertyChangedCallback(DocumentChanged)));

private static void DocumentChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("Document has changed");
}

public FlowDocument Document
{
get { return GetValue(DocumentProperty) as FlowDocument; }
set { SetValue(DocumentProperty, value); }
}
}

But…… OMG! why it’s so hard to use FlowDocument? How come do we need to call Content.Start and End just to get the text? why not having a property called Text which is a string datatype?

Yes. it’s ture that using FlowDocument is not so simple compared to a string datatype. we also got the same feeling when we were implemneting this feature. what did we do? We decided to change Document property, a FlowDocument type, to “BoundDocument” which is a string datatype. So, the new code will be like that below. As you can see, it’s a bit complicated then before since we are handling all complex things there.


public static class RichTextboxAssistant
{

public static readonly DependencyProperty BoundDocument =
DependencyProperty.RegisterAttached("BoundDocument", typeof(string), typeof(RichTextboxAssistant),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnBoundDocumentChanged));

private static void OnBoundDocumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{

RichTextBox box = d as RichTextBox;

if (box == null)
return;

RemoveEventHandler(box);

string newXAML = GetBoundDocument(d);

box.Document.Blocks.Clear();

if (!string.IsNullOrEmpty(newXAML))
{

using (MemoryStream xamlMemoryStream = new MemoryStream(Encoding.ASCII.GetBytes(newXAML)))
{

ParserContext parser = new ParserContext();
parser.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
parser.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
FlowDocument doc = new FlowDocument();

Section section = XamlReader.Load(xamlMemoryStream, parser) as Section;
box.Document.Blocks.Add(section);
}

}

AttachEventHandler(box);

}

private static void RemoveEventHandler(RichTextBox box)
{

Binding binding = BindingOperations.GetBinding(box, BoundDocument);

if (binding != null)
{
if (binding.UpdateSourceTrigger == UpdateSourceTrigger.Default ||
binding.UpdateSourceTrigger == UpdateSourceTrigger.LostFocus)
{
box.LostFocus -= HandleLostFocus;
}
else
{
box.TextChanged -= HandleTextChanged;
}
}

}

private static void AttachEventHandler(RichTextBox box)
{

Binding binding = BindingOperations.GetBinding(box, BoundDocument);
if (binding != null)
{
if (binding.UpdateSourceTrigger == UpdateSourceTrigger.Default ||
binding.UpdateSourceTrigger == UpdateSourceTrigger.LostFocus)
{
box.LostFocus += HandleLostFocus;
}
else
{
box.TextChanged += HandleTextChanged;
}
}

}

private static void HandleLostFocus(object sender, RoutedEventArgs e)
{

RichTextBox box = sender as RichTextBox;
TextRange tr = new TextRange(box.Document.ContentStart, box.Document.ContentEnd);
using (MemoryStream ms = new MemoryStream())
{
tr.Save(ms, DataFormats.Xaml);
string xamlText = ASCIIEncoding.Default.GetString(ms.ToArray());
SetBoundDocument(box, xamlText);
}

}

private static void HandleTextChanged(object sender, RoutedEventArgs e)
{

// TODO: TextChanged is currently not working!
RichTextBox box = sender as RichTextBox;
TextRange tr = new TextRange(box.Document.ContentStart,
box.Document.ContentEnd);

using (MemoryStream ms = new MemoryStream())
{
tr.Save(ms, DataFormats.Xaml);
string xamlText = ASCIIEncoding.Default.GetString(ms.ToArray());
SetBoundDocument(box, xamlText);
}

}

public static string GetBoundDocument(DependencyObject dp)
{
return dp.GetValue(BoundDocument) as string;
}

public static void SetBoundDocument(DependencyObject dp, string value)
{
dp.SetValue(BoundDocument, value);
}

}

Yes. That’s it. You can now simply bind this attached property with a string instead of a flow document.


<RichTextBox  attached:RichTextboxAssistant.BoundDocument="{Binding Text}" Height="92" />

RichText Editor

After implementing the binding support for WPF RichTextbox, we got new requirement that we need to develop a RichText Editor (something like TinyMCE) as well. So, we quickly create a new user control called RichTextEditor.xaml and place a RichTextbox with our attached property. After a few minutes, we got a WPF RichText Editor as below. ( As there are a lot of code snippets already in this post, I’m not going to post it here. Please feel free to take a look at RichTextEditor.xaml in sample project. )

wpf-rich-text-editor1

HTML to XAML Conversion

Our manager was quite happy with our quick and cool solution for implementing WPF Rich Textbox so we checked-in the changes that we made to SVN. and then, the continous integration integrated our latest changes into the new build so people from QA can start testing on our new feature.

After a few hours, we started getting new bugs regarding to our new RichText Editor from QA. Ouch!

What happened was that there is one ASP.NET website that is using the same service and same table. The ASP.NET team is using TinyMCE, a Javascript WYSIWYG Editor in that website so those HTML tags which are the output of that editor are being saved in database. That’s why our WPF RichText Editor wasn’t able to render those HTML tags. The same way, their TinyMCE was also having problems with our XAML tags.

wysiwyg-javascript-editor

So, what should we do? Ha! I can tell that what’s in your mind now. Yes. a converter! What we need here is a converter that can convert HTML to XAML (vise-versa). Luckily, Microsoft provides a set of classes that can do the conversion for you. You can grab a copy of those classes from this link. (Thank you! MS). We embedded those classes in our application and changed our code as below to support the conversion.


public static string GetBoundDocument(DependencyObject dp)
{
var html = dp.GetValue(BoundDocument) as string;
var xaml = string.Empty;

if (!string.IsNullOrEmpty(html))
xaml = HtmlToXamlConverter.ConvertHtmlToXaml(html, false);

return xaml;
}

public static void SetBoundDocument(DependencyObject dp, string value)
{
var xaml = value;
var html = HtmlFromXamlConverter.ConvertXamlToHtml(xaml, false);
dp.SetValue(BoundDocument, html);
}

That’s is. I already attached all sourcecode in the zip file. Please feel free to download it and play as much as you like. But hey! don’t forget to give the feedback if you found something uncool!.

Here is how my sample looks like. Happy Wpf-ing!!! :)

wpf-super-cool-rich-text-editor

32 thoughts on “Bindable WPF RichText Editor with XAML/HTML Convertor

  1. Reply Philipp Sumi Jun 9,2009 3:29 pm

    Damn Michael,

    I’ve being doing the *exact* same work (RTF editor with toolbar, along with XAML2HtmlConverter for HTML roundtripping) on a customer project the last few days. If I only had known…

    Still – looking forward to comparing and maybe even stealing some ideas from your approach :)

    Cheers,
    Philipp

  2. Reply Michael Sync Jun 9,2009 7:02 pm

    HI Philip,

    :) yes. I love to see your approach as well. By the way, Did you notice that there is one problem “unsupported” things in my code?

    I’m not able to provide PropertyChanged binding in my attached property. :( Please let me know if you have any idea to solve that problem. :)

  3. Reply Gabriel Valencia Jun 11,2009 10:37 am

    Hi Michael!

    Excelent article, but I need to read from a DB Table a field that contain the rtf code, and also if the user change the content return the rtf code to cans ave to the DB. what do I need to change or remove?

  4. Reply urza Jun 21,2009 2:15 pm

    Hello, very nice work. Could you change it to VS 2008 solution please?

  5. Reply Michael Scherf Jun 30,2009 3:53 am

    Hello Michael,

    did you find a workaround for the PropertyChanged binding? I’ve got the problem that if I change the binding inside your RichTextEditor control for the RichTextBox to PropertyChanged, the caret jumps to the beginning of the document and I’m not able to set a new CaretPosition inside HandleTextChanged method. Is there a suitable workaround to restore the caret position?

    Greetings, Michael

  6. Reply Tim Jul 1,2009 9:43 am

    Maybe I’m missing something, but after downloading the project, I don’t really see any binding going on. As a test, I changed the one line in ShowText():

    Text = “Changed”

    When I click the button, nothing happens. Shouldn’t the binding cause the RichTextBox to show that new string?

  7. Reply tom Aug 24,2009 2:23 am

    Great work michael. Any I dea how i could run it 2008? I know you have probably has loads of guys already ask you.

    All the best

    Tom

  8. Reply Manu Sep 17,2009 1:00 am

    Bug 1: é à è … not is working, change encoding to repair
    class RichTextboxAssistant:
    line 29using (MemoryStream xamlMemoryStream = new MemoryStream(Encoding.UTF8.GetBytes(newXAML)))
    line 103+118 : string xamlText = Encoding.UTF8.GetString(ms.ToArray());

    Bug 2 : not working
    class HtmlFromXamlConverter:
    Line 450
    case “LineBreak”:
    htmlElementName = “BR”;
    break;

  9. Reply Biper Sep 17,2009 1:18 am

    I have fixed the problem with TextChanged. You will have to add one more field with flag like in TextBox.

  10. Reply Michael Scherf Oct 12,2009 12:43 am

    @Biper

    could you post the source code that fixed the TextChanged issue? Thank you!

  11. Reply Alex Nov 12,2009 4:10 am

    Excellent project. I’ve got an update/suggestion on hiding the toolbar for ReadOnly purposes. If anybodies interested.

    Add to <ToolBar Visibility="{Binding Path=ToolbarVisible, ElementName=uxRichTextEditor}"

    Code behind add the following:

    public static readonly DependencyProperty ToolbarVisibilityProperty =
    DependencyProperty.Register("ToolbarVisibility", typeof(Visibility), typeof(RichTextEditor),
    new PropertyMetadata(true));

    public Visibility ToolbarVisibility
    {
    get { return (Visibility)GetValue(ToolbarVisibilityProperty); }
    set
    {
    SetValue(ToolbarVisibilityProperty, value);
    }
    }

  12. Reply Terrence Ward Nov 16,2009 7:15 pm

    This is great work, I was wondering if you were able to add table support? Or if you could give me a headsup how I would implement it myself…

  13. Reply TimOther Nov 18,2009 1:13 pm

    I would also like to see the source that fixed the TextChanged issue.

    Thanks.

  14. Reply Zoopaman Dec 2,2009 9:21 am

    Hi! I’ve wanted to try this out on a string, but I’m getting an XAMLParseException. How do I modify the string (to XML?) – what tags/nodes are needed?

    Thanks in advance!

  15. Reply Chris McG Dec 2,2009 8:29 pm

    Hi, great article. I’m having trouble getting a hyperlink to work in it. I added mainRTB.Document.AddHandler(Hyperlink.ClickEvent, new RoutedEventHandler(HandleHyperlink)); in the constructor and it didn’t work. Any ideas?

  16. Reply Linda Rawson Dec 11,2009 1:51 pm

    Thanks so much for all of your work. I have a somewhat unique problem I need two things really. A spell checker and the ability to paste and image into the rtf (which it does) and save it out.

    Thanks!
    Linda Rawson

  17. Reply Muaath Jamil Feb 4,2010 4:56 am

    Your control is soo great,,
    I get a very huge benifit from it…

    now i’m trying to use your control (TextEditor) inside my UserControl…
    But I faced a very strange behaviour..
    when I try to edit the text inside the TextEditor:
    The Line In The Control accept only one character and continue the next character in the next line (although the rest of the line is empty)..
    for exampe..
    when i try to write (Hello World!!) it will displayed as:
    H
    e
    l
    l
    o

    W
    o
    r
    l
    d
    !
    !

    — I Tried alot of things but witout any success..
    When I use it inside the page or window directly it works fine,, the problem occure only when using the control inside a UserControl..

    I Appreciate any help..
    Thank You alot *_*

  18. Reply Michel R. Feb 23,2010 1:05 pm

    I’d also like to see the source for the fixed TextChanged.

    Great job on the control – very nicely done!

    Thanks.

  19. Reply Matt Penner Apr 10,2010 1:51 am

    I’m trying your code but for some reason the text that is bound always is displayed without any formatting. I’m not using your RichTextEditor control but rather just the attached property (and its dependencies). All I’m really trying to do is display formatted text. It is not going to be editable.

    I have html tags in the text yet they are simply stripped out. They aren’t even displayed. So “this should be bold” simply comes out as “this should be bold”.

    I’m not sure what I am missing.

    Unfortunately I cannot run your code becuase I don’t have VS 2010 or .Net 4 installed.

    I tried loading your files into a VS 2008 project but I’m getting weird errors that I can’t figure out.

    Anyway, if you could let me know why my bound text is not getting formatted I would appreciate it.

    Thanks,
    Matt

  20. Reply Julian Dominguez May 11,2010 6:07 am

    Hi Mike, great article. The XAML2HTML converter from the SDK is rather rough and looses a lot of quality. Did you get a chance to update it and fix some of its issues? I’d love to see it improved!

  21. Reply Martin Eddington Jul 7,2010 3:53 am

    Hi, just to say the problem with the text flowing downwards, not across is caused by placing the control within other controls which need to know the width of their children. Wrapping it in a scrollviewer or other controls is an incomplete fix for more complex scenarios.

    The way to fix it is to bind the internal Flowdocument in the RichTextBox to the width of the RichTextBox.

    e.g.

    I’m currently driving myself insane trying to make the TextChanged property work correctly… I’ll post again if I fix it.

  22. Reply Martin Eddington Jul 7,2010 3:56 am

    Sorry, WordPress doesn’t like this code – enclose it in open and close XML tags…

    FlowDocument PageWidth=”{Binding ElementName=mainRTB, Path=ActualWidth}”

  23. Reply Loïc Joly Jul 28,2010 7:13 am

    Hello,

    I struggled with the HandleTextChanged a little bit, and found this solution (the rest is unchanged):

    private static void OnDocumentTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {

    RichTextBox box = d as RichTextBox;
    if (box == null)
    return;

    if (ignoredTextBoxes.Contains(box))
    {
    return;
    }
    // Just as before…
    }

    private static void HandleTextChanged(object sender, RoutedEventArgs e)
    {
    RichTextBox box = sender as RichTextBox;
    TextRange tr = new TextRange(box.Document.ContentStart, box.Document.ContentEnd);

    using (MemoryStream ms = new MemoryStream())
    {
    tr.Save(ms, DataFormats.Xaml);
    string xamlText = UTF8Encoding.Default.GetString(ms.ToArray());
    ignoredTextBoxes.Add(box);
    SetDocumentText(box, xamlText);
    ignoredTextBoxes.Remove(box);
    }

    }

    static HashSet ignoredTextBoxes = new HashSet();

  24. Reply Sarah Dec 13,2010 10:12 am

    Looks like FlowDocument doc = new FlowDocument() in OnBoundDocumentChanged() is never used.

  25. Reply Brian Lagunas Feb 7,2011 11:58 am

    This is a nice post, but you could save yourself some time and just use the RichTextBox in the Extended WPF Toolkit. It has all this functionality built into it, minus the HTML Conversion. But you can create your own custom ITextFormatter using the code in this article. There is also a RichTextBoxFormatBar which mimics MS Office contextual format bar.

    wpftoolkit.codeplex.com

  26. Reply PB from internet marketing Jun 13,2011 4:38 am

    Thanks so much for all of your work.

  27. Reply NL Jul 29,2011 3:08 pm

    How to highlight option to tool bar, so that users can add color to text ?

  28. Reply giocaputo Aug 10,2011 5:49 am

    It resolved setting only width propery to richtextbox control. For example

  29. Reply Shimmy Mar 5,2012 6:21 pm

    Please make available via NuGet.
    Thanks

  30. Reply yasin Mar 22,2012 2:09 am

    hi ı need use turkhish character so

    we use
    html meta code but
    ı dont use this project ,
    Where do I need to add
    help me pls,

  31. Reply Sathish May 22,2013 10:16 pm

    is it possible to edit xhtml file using this editor.
    if yes

    how it will map the CSS.

    kindly guide me

    waiting for your response………………………….

    Thanks
    Sathish

  32. Reply Michael Sync May 23,2013 12:09 am

    If you just want HTML editor then why don’t you use other mature HTML editor?

Leave a Reply