Diederik Krols

The XAML Brewer

A Rich HTML TextBlock for WPF

This article -proudly- presents a WPF control that displays rich text from an HTML source: it has a Text dependency property that you can bind to a HTML-formatted string. It's designed to display -not edit- rich information messages like warnings, error messages, and other feedback e.g. from a localizable resource file. Here are a couple of examples, on the left side is the original HTML, on the right side is how it's displayed by the control:

Formatting:

Font sizes:

Colors:

Some issues with WPF's native RichTextBox control.

The HtmlTextBlock control is based on WPF's native RichTextBox control. This was not an obvious choice, since RichTextBox was actually designed as an editor control: by typing in the box and pressing toolbar buttons, a FlowDocument is created and displayed. This FlowDocument is accessible through the Document property, which unfortunately is

  • very complex to manipulate, compared to a formatted HTML or XAML string (you can't store it in a resource file), and
  • not a dependency property.

On the other hand, the FlowDocument has powerful text formatting features, so it really makes sense to try to leverage these.

Making a bindable RichTextBox

If you grab your favorite search engine and type "WPF Bindable RichTextBox" you'll find plenty of implementations. This one on CodePlex nicely explains the problems ànd solutions. Basically all you need to do is write a dependency property that is wrapped around the RichtTextBox' FlowDocument whilst converting it to and from a XAML-formatted string. Here's how this process looks like in code:

Dependency Property

These code snippets define the dependency property:

public static readonly DependencyProperty TextProperty = DependencyProperty.Register(

    "Text",

    typeof(string),

    typeof(RichTextBox),

    new FrameworkPropertyMetadata(

        String.Empty,

        FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,

        new PropertyChangedCallback(OnTextPropertyChanged),

        new CoerceValueCallback(CoerceTextProperty),

        true,

        System.Windows.Data.UpdateSourceTrigger.LostFocus));

 

public string Text

{

    get { return (string)GetValue(TextProperty); }

    set { SetValue(TextProperty, value); }

}

Conversion from XAML to FlowDocument

This method takes a XAML-formatted string and loads it into a FlowDocument:

public void SetText(System.Windows.Documents.FlowDocument document, string text)

{

    try

    {

        TextRange tr = new TextRange(document.ContentStart, document.ContentEnd);

        using (MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(text)))

        {

            tr.Load(ms, DataFormats.Xaml);

        }

    }

    catch

    {

        throw new InvalidDataException("data provided is not in the correct Xaml format.");

    }

}

Conversion from FlowDocument to XAML

This function does the opposite. It saves a FlowDocument as a XAML-formatted string:

public string GetText(System.Windows.Documents.FlowDocument document)

{

    TextRange tr = new TextRange(document.ContentStart, document.ContentEnd);

    using (MemoryStream ms = new MemoryStream())

    {

        tr.Save(ms, DataFormats.Xaml);

        return ASCIIEncoding.Default.GetString(ms.ToArray());

    }

}

The above code snippets originate from this RichtTextBox implementation from CodePlex (more about that later).

From TextBox to TextBlock

Since my control is a display panel instead of an edit box, I had to tweak the code in several places. Some extra properties were set in the constructor:

public RichTextBox()

{

    Loaded += RichTextBox_Loaded;

 

    //Added

    this.IsReadOnly = true;

    this.Focusable = false;

}

The original CodePlex control was not 100% percent bindable to a source: it was just Initializable from a source. After the source provided the initial value, all other updates were ignored. That was not compatible with my requirements: I wanted to bind the control to a dynamic value, like changing status information or an on-the-fly-localizable message. So I removed the code that inhibited long-term binding: the definition and all references and manipulation of the _textHasLoaded variable. Now the control does more, with less code.

All you need to do is bind the textbox to its source:

<uc:RichTextBox

   Text="{Binding Message}"

</uc:RichTextBox>

And on every update of the source, the displayed text will also be updated:

this.Message = "The report was sent to <b>your local printer</b>.<br/><br/>Press <u style='color:red'>Enter</u> to continue or <u style='color:red'>Back</u> to print again.";

 

Here's the result:

Converting HTML to XAML, and back

The native WPF RichTextBox nicely displays plain text, RTF, and XAML, but no HTML. Fortunately a lot of attempts were already made to convert HTML into XAML and back. The code I'm currently using comes from MSDN. Its probably not the world's best converter, but it simply does what I was looking for: formatting (bold, italic, underline, size, color, and line breaks). It's not the best solution if you want to properly display tables, lists, headers and so. A more powerful convertor can be found on CodePlex in the HTML 2 RTB XAML project, which in its turn is based on HTML Agility Pack. Unfortunately the conversion code in the former project is tightly coupled to (Silverlight) controls. I didn't have the time to refactor this. (Take that as a hint Smile)

Making the RichTextBox understand and speak HTML

The RichTextBox from the Extended WPF Toolkit -again from CodePlex- not only has a Text dependency property for data binding. It also introduces the concept of Text Formatters, allowing to declaratively and programmatically specify the format of the content. The three canonical Text Formatters are included; PlainTextFormatter, RtfFormatter, and XamlFormatter.

All I did was taking the code of the XamlFormatter and plugged in the Html converter. I also replaced the ASCII encoding by UTF8 encoding, since French text wasn't displayed correctly.

Here's the full code of the HTML-to-FlowDocument-and-back converter:

public class HtmlFormatter : ITextFormatter

{

    public string GetText(System.Windows.Documents.FlowDocument document)

    {

        TextRange tr = new TextRange(document.ContentStart, document.ContentEnd);

        using (MemoryStream ms = new MemoryStream())

        {

            tr.Save(ms, DataFormats.Xaml);

            return HtmlFromXamlConverter.ConvertXamlToHtml(UTF8Encoding.Default.GetString(ms.ToArray()));

        }

    }

 

    public void SetText(System.Windows.Documents.FlowDocument document, string text)

    {

        text = HtmlToXamlConverter.ConvertHtmlToXaml(text, false);

        try

        {

            TextRange tr = new TextRange(document.ContentStart, document.ContentEnd);

            using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(text)))

            {

                tr.Load(ms, DataFormats.Xaml);

            }

        }

        catch

        {

            throw new InvalidDataException("data provided is not in the correct Html format.");

        }

    }

}

Architecture

The following UML class diagram reveals all major component of the architecture:

Source Code

The included project contains the control, and a sample client.

Here's how the client application looks like:

Here's the code: U2UConsult.HtmlTextBlock.Sample.zip (91,62 kb)

Enjoy!

Comments (15) -

  • Yevgeniy

    10/10/2011 9:30:48 PM |

    Nice, very nice...

  • Apurva

    11/9/2011 4:11:37 AM |

    Your control is very nice, the only problem I am having is that when the RichTextBox is part of a DataTemplate, it stops rendering the text as Html.

    Any suggestions?
    Thanks

  • yasin

    2/22/2012 7:35:24 PM |

    tank you for this paper.

    so we use html,css for desing wpf ??

  • Eze

    4/2/2012 6:50:55 AM |

    Someone can make it work inside a DataTemplate?

  • Hicham

    10/12/2012 10:45:50 AM |

    Thanks for the control, very nice work.
    As workaround for the DataTemplate rendering problem, you can set the HtmlFormatter as default Formatter in the TextFormatter Property.

    Hicham

  • Birki

    10/15/2012 11:22:09 PM |

    Is this open source i.e. GPL or LGPL?

  • Diederik Krols

    10/17/2012 9:06:38 PM |

    @Birki: this is just a blog article. You're free to use the code you find here.

  • Hari

    10/23/2012 8:14:55 PM |

    Is it possible modified tag <u> for underline text , give it a space between word and underline ? What can i do for that ?

    Thanks.

  • Matt

    12/17/2012 1:34:45 AM |

    Nice work, the only thing that makes the whole work in one snap bad ->

    <B>bla<b
    blabla

    Will crash the whole thing, cause of non closed brackets

  • Daniel

    2/6/2013 8:57:27 PM |

    Hi,

    Lovely work. Thank you.

    I changed the constructor of the RichTextBox to make 'this.Focusable = true', instead of false. I wanted the text in the RTB to be selectable and this now allows that. If you think this will blow up in my face and you ever notice this comment, and you have a spare moment, then let me know!

    Regards,

    Daniel

  • César F. Qüeb

    4/19/2013 6:14:08 AM |

    Congratulations for this amazing article, really useful and appreciated. Using the Framework 3.5 with VS 2010 requires that you add following lines of code:

    //Constructor section, next the InitalizeComponent():

    Microsoft.Windows.Controls.HtmlFormatter htmlFormatter= new Microsoft.Windows.Controls.HtmlFormatter();
    TextFormatter = htmlFormatter;  // you can place into the consumer applcation side,
                                    //at you assign to control.

    Regards and Thanks a lot for share with us this gift.

  • Panos

    1/8/2014 10:50:24 PM |

    Excellent article! I know it is an old thread, but can you please tell me how to make the HTML hyperlinks clickable, within the RichTextBox control?
    Thanks a bunch!

  • Diederik Krols

    1/9/2014 2:35:12 AM |

    Hi Panos,
    that would involve drastically modifying the structure of the HtmlToXaml converter. You would have to insert HyperLink controls instead of Runs for the anchor tags. It might be easier to kind of flag the hyperlinks in the text, then find them with a regular expression, and insert an Hyperlink control in the content. That is more or less what I do here: blogs.u2u.be/.../...for-Windows-81-Store-apps.aspx

  • Loren Notik

    3/2/2014 11:39:00 AM |

    I was recommended this web site by my cousin. I am not sure whether this post is written by him as no one else know such detailed about my trouble. You are incredible! Thanks!

  • Lacie Tribou

    3/27/2014 11:16:01 PM |

    Hi there, after reading this amazing article i am too cheerful to share my familiarity here with colleagues.

Comments are closed