Click here to Skip to main content
11,575,925 members (59,761 online)
Click here to Skip to main content

WPF RichText Editor

, 10 Jun 2009 CPOL 192.8K 9.7K 232
Rate this:
Please Sign up or sign in to vote.
Bindable WPF WYSIWYG Text Editor

Dependencies

Introduction

This post will give you some tips/tricks of using RichTextbox in WPF. As we all know, the built-in WPF RichTexbox doesn't provide some features that we are looking for, so if you are in need of using RichTexbox in a 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 RichTextbox Editor with toolbar.

Contents

  • Bindable RichTextbox
  • RichText Editor
  • HTML to XAML Conversion
wpf-rich-text-editor

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 have a custom property that can be bindable in that control so the first thing that comes to my mind is the attached property. There may be a lot of definitions for it but the way I understand is that it is a custom property that can be attached to the control. For example: AA property to B Control, 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, 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 we need to call Content.Start and End just to get the text? Why not have a property called Text which is a string datatype?

Yes. it's true that using FlowDocument is not so simple compared to a string datatype. we also got the same feeling when we were implementing 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 the one shown below. As you can see, it's a bit complicated than 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 a new requirement that we need to develop a RichText Editor (something like TinyMCE) as well. So, we quickly created 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 the sample project.)

wpf-rich-text-editor1

HTML to do 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 continuous integration integrated our latest changes into the new build so people from QA could start testing our new feature.

After a few hours, we started getting new bugs regarding 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 the 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 what's on your mind now. Yes. a converter! What we need here is a converter that can convert HTML to XAML (vice-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! Microsoft) 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 it. I already attached all source code 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!!! Smile | :)

wpf-super-cool-rich-text-editor

License

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

Share

About the Author

Michael Sync
Software Developer (Senior)
Singapore Singapore
Michael Sync is a Microsoft MVP for Silverlight and a member of Microsoft WPF/Silverlight Insider Team.

Please find more details about me in my blog.

You may also be interested in...

Comments and Discussions

 
QuestionGreat article Pin
dejan19dejan198-Jun-15 4:52
memberdejan19dejan198-Jun-15 4:52 
QuestionHow is it actually bound to rich text control? Pin
shamsz14-Apr-15 4:42
membershamsz14-Apr-15 4:42 
QuestionProblem with hebrew text Pin
galisson26-Jan-14 1:39
membergalisson26-Jan-14 1:39 
Questionnumbering/bulleting not displaying in RichTextEditor Pin
Member 1034904620-Oct-13 23:52
memberMember 1034904620-Oct-13 23:52 
GeneralMy vote of 2 Pin
Josh_T3-Jan-13 16:19
memberJosh_T3-Jan-13 16:19 
SuggestionSystem.Xaml.dll needed Pin
superryan3-Oct-12 14:53
membersuperryan3-Oct-12 14:53 
GeneralRe: System.Xaml.dll needed Pin
Kamran Behzad23-Jan-14 19:09
memberKamran Behzad23-Jan-14 19:09 
Questionshow symbols instead of accents Pin
Member 804474022-Sep-12 5:45
memberMember 804474022-Sep-12 5:45 
AnswerRe: show symbols instead of accents Pin
IImmortal9-Jan-13 1:42
memberIImmortal9-Jan-13 1:42 
GeneralMy vote of 5 Pin
dipal_bhavsar11-Sep-12 0:57
memberdipal_bhavsar11-Sep-12 0:57 
Questionsaving richtextbox content Pin
Farhan Ghumra19-Jul-12 1:16
memberFarhan Ghumra19-Jul-12 1:16 
GeneralMy vote of 5 Pin
Farhan Ghumra19-Jul-12 1:15
memberFarhan Ghumra19-Jul-12 1:15 
QuestionCould you clean unnecessary <SPAN>? Pin
Simon.P.G.4-Mar-12 2:31
memberSimon.P.G.4-Mar-12 2:31 
GeneralMy vote of 5 Pin
prromap25-Jan-12 1:27
memberprromap25-Jan-12 1:27 
Just KISS principle!
QuestionHypenation Pin
brevus19-Sep-11 3:03
memberbrevus19-Sep-11 3:03 
GeneralMy vote of 3 Pin
urvishapandya29-Mar-11 4:29
memberurvishapandya29-Mar-11 4:29 
GeneralMy vote of 5 Pin
ChrDressler12-Feb-11 0:32
memberChrDressler12-Feb-11 0:32 
GeneralMy vote of 1 Pin
suslicek6-Feb-11 22:24
membersuslicek6-Feb-11 22:24 
GeneralDoesn't seem to like horizontally oriented stack panels Pin
sameeraperera21-Sep-10 20:24
membersameeraperera21-Sep-10 20:24 
GeneralMy vote of 5 Pin
koolprasad20033-Sep-10 21:45
memberkoolprasad20033-Sep-10 21:45 
GeneralDownloaded Projet won't compile in VC# express [modified] Pin
xzz019524-Mar-10 6:30
memberxzz019524-Mar-10 6:30 
GeneralRe: Downloaded Projet won't compile in VC# express Pin
xzz019524-Mar-10 7:15
memberxzz019524-Mar-10 7:15 
General[My vote of 2] Didn't work for me Pin
David Veeneman15-Mar-10 7:01
memberDavid Veeneman15-Mar-10 7:01 
GeneralRe: [My vote of 2] Didn't work for me Pin
Michael Sync15-Mar-10 15:32
memberMichael Sync15-Mar-10 15:32 
GeneralRe: [My vote of 2] Didn't work for me [modified] Pin
vnkd28-Feb-11 9:38
membervnkd28-Feb-11 9:38 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150603.1 | Last Updated 10 Jun 2009
Article Copyright 2009 by Michael Sync
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid