Making Hyperlinks and Dynamic RichTextBox Content Play Nice in WPF


I’ve been working on a WPF project for some time now, and one of the specifications required that the application be able to render web content without a full blown browser. This actually is a fairly non-trivial task, especially if you want to render the content in something simple like a RichTextBox control. Thankfully, Nigel Spencer has a great blog post about using Microsoft’s HtmlToXaml converter, which really makes this much simpler.

However, even after I had successfully converted the HTML content into a FlowDocument which rendered correctly in my RichTextBox control, I was left with one lingering issue: Hyperlinks didn’t work! After some research I found that this is actually a fairly common roadblock, with many different takes on the solution.

My personal approach is simple:

  1. Iterate through all of the elements of the RichTextBox.Document
  2. Ensure that the iteration happens whenever the RichTextBox content changes
  3. If a hyperlink is found, hook into an event handler to launch the link in an external browser

First, we need to decide upon an event in order to catch the RichTextBox content changes.  I chose RichTextBox.TextChanged as it fires when the initial content is set, as well as on all subsequent changes.  You must also ensure that the IsDocumentEnabled property of your RichTextBox control is set to true.

Next, in order to iterate the FlowDocument elements, I used a bit of the code found in a blog post by Dan Lamping.  I then combined that with a simple hook to the Hyperlink.RequestNavigate event.

public MainWindow()
{
	InitializeComponent();

	myRichTextBox.TextChanged += HookHyperlinks;
}

private void HookHyperlinks(object sender, TextChangedEventArgs e)
{
	FlowDocument doc = (sender as RichTextBox).Document;

	for (TextPointer position = doc.ContentStart;
		position != null && position.CompareTo(doc.ContentEnd) <= 0;
		position = position.GetNextContextPosition(LogicalDirection.Forward))
	{
		if (position.GetPointerContext(LogicalDirection.Forward) ==
			TextPointerContext.ElementEnd)
		{
			if (position.Parent is Hyperlink)
				(position.Parent as Hyperlink).RequestNavigate += HandleRequestNavigate;
		}
	}
}

private void HandleRequestNavigate(object sender, RequestNavigateEventArgs args)
{
	   System.Diagnostics.Process.Start(args.Uri.ToString());
}

At this point we have a functional setup.  Whenever the RichTextBox.TextChanged event fires, the content is parsed and any Hyperlink elements are hooked to our HandleRequestNavigate handler, which in turn simply uses Process.Start() to launch the default browser towards the given uri.

,