C# – Insert Images and Hyperlinks into RichTextBox in WPF

Like the name of control, the RichTextBox inheriting from TextBox control, allows user to enter and edit text. However it also provides more advanced formatting features than the standard TextBox, for example inserting rich media objects like images and hyperlinks. The objects (text, images, hyperlinks…) can be assigned directly to the control or can be loaded from a rich text format (RTF) file. We can also load data from an already opened data stream, edit and then save its content to RTF or ASCII file format. Moreover the RichTextBox control provides a number of properties we can use to apply formatting to any portion of text within the control as font, color, indent… In this small blog post I would like to demonstrate how I can parse Smiley code and Hyperlink in a RichTextBox.

After creating a normal WPF application, insert a TextBox and a RichTextBox as following XAML code

<Window x:Class="RichTextBox_Hyperlink.MainWindow"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	xmlns:local="clr-namespace:RichTextBox_Hyperlink"
	mc:Ignorable="d"
	Height="300"
	Width="300"
	Title="RichTextBox - Images and Hyperlinks"
	DataContext="{Binding Main, Source={StaticResource Locator}}">
...
<Grid x:Name="LayoutRoot">
	<Grid.RowDefinitions>
		<RowDefinition Height="5*"/>
		<RowDefinition Height="5*"/>
	</Grid.RowDefinitions>
	<TextBox x:Name="TextWithUrl" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" AcceptsReturn="True" Grid.Row="0" />
	<RichTextBox IsDocumentEnabled="True" Grid.Row="1" local:RTBNavigationService.Content="{Binding Text, ElementName=TextWithUrl}">
	</RichTextBox>
</Grid>

My demo contains simply a TextBox for inputting plain text and the RichTextBox control will parse this plain text as I want. Therefore I bound the Text property of RichTextBox to the Text property of TextBox control. As you know that the Text property of RichTextBox in WPF is “invisible”. To “hook up” its content, I have to write a class with a DependencyProperty bound to this Text property and handle its Changed event to modify the text as it should be displayed

public static class RTBNavigationService
{
	public static string GetContent(DependencyObject d)
	{ return d.GetValue(ContentProperty) as string; }

	public static void SetContent(DependencyObject d, string value)
	{ d.SetValue(ContentProperty, value); }

	private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
	{
	...
	}
}

Now I have “hooked” the Changed event of Text property, then I go to the first Parse function: Smiley Code Parsing.

1. Smiley Code Parsing
As every Internet citizen knows, each smiley is bound with a smiley code. For each application there is a other smiley code set. In this small demo I just want to handle only one smiley code : ). That means when I type in TextBox : ), then the RichTextBox should show a smile icon.

private static readonly Regex regexSmilies = new Regex(@"(:\)(?!\)))");
private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
	RichTextBox richTextBox = d as RichTextBox;
	if (richTextBox == null)
		return;

	
	string content = (string)e.NewValue;
	if (string.IsNullOrEmpty(content))
		return;

	richTextBox.Document.Blocks.Clear();

	int lastPos = 0;
	Paragraph block = new Paragraph();
	foreach (Match match in regexSmilies.Matches(content))
	{                
		if (match.Index != lastPos)
			block.Inlines.Add(content.Substring(lastPos, match.Index - lastPos));

		BitmapImage bitmapSmiley = new BitmapImage(new Uri("giggle.gif", UriKind.Relative));
		Image smiley = new Image();
		smiley.Source = bitmapSmiley;
		smiley.Width = bitmapSmiley.Width;
		smiley.Height = bitmapSmiley.Height;
		block.Inlines.Add(smiley);
		
		lastPos = match.Index + match.Length;                     
	}
	if (lastPos < content.Length)
		block.Inlines.Add(content.Substring(lastPos));
	richTextBox.Document.Blocks.Add(block);
	
	...
}

The code is pretty simple. First I need to find where the smiley code was entered. Then I just split the Text into 3 parts: before smiley, smiley and after smiley. The parts – before and after smiley – will be inserted as Text through the AppendText function. The smiley code will be firstly replaced with an image and then inserted into the paragraph. Appending these parts with each other I have something like this

RichTextBox - Images and Hyperlinks

2. Hyperlink Parsing
In the Smiley parsing, I use a paragraph to store and then append the objects (text, image) with each other. You can use this technique to parse hyperlink too. However I would like to introduce another way with TextPointer to parse hyperlinks in RichTextBox text.
The idea is same as above, I need to find all hyperlinks with Regex and then replace the text with a real hyperlink one which one can click on it. But instead of adding directly hyperlink object into a paragraph, I will mark these hyperlinks with TextPointer and create a hyperlink object for it.

private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
	...
	List<Hyperlink> results = new List<Hyperlink>();
	foreach (Match match in regexUrl.Matches(content))
	{
		TextPointer p1 = richTextBox.ToTextPointer(match.Index);
		TextPointer p2 = richTextBox.ToTextPointer(match.Index + match.Length);
		if (p1 == null || p2 == null)
		{
			//Donothing
		}
		else
		{
			(new Hyperlink(p1, p2)).Click += OnUrlClick;
		}
	}
}

private static void OnUrlClick(object sender, RoutedEventArgs e)
{
	Process.Start((sender as Hyperlink).NavigateUri.AbsoluteUri);
}

As you can see that I convert the start and end offsets of hyperlink to TextPointer and create a object of hyperlink with these TextPointers inclusive adding a Click handler for it. The ToTextPointer () function walks through all components of RichTextBox and converts the start/end offset of hyperlink to TextPointer.

public static TextPointer ToTextPointer(this RichTextBox rtb, int index)
{
	int count = 0;
	TextPointer position = rtb.Document.ContentStart.GetNextContextPosition(LogicalDirection.Forward).GetNextContextPosition(LogicalDirection.Forward);
	while (position != null)
	{
		if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
		{
			string textRun = position.GetTextInRun(LogicalDirection.Forward);
			int length = textRun.Length;
			if (count + length > index)
			{
				return position.GetPositionAtOffset(index - count);
			}
			count += length;
		}
		position = position.GetNextContextPosition(LogicalDirection.Forward);
	}
	return null;
}

At the end we have a RichTextBox which can parse smiley code and hyperlink dynamically. It’s very comfortable when using MVVM with data binding

RichTextBox - Images and Hyperlinks

The demo source code can be downloaded here “RichTextBox – Images and Hyperlinks

4 thoughts on “C# – Insert Images and Hyperlinks into RichTextBox in WPF”

  1. @mike: The error happens in block code for handling the URL. Remove the Replace(Environment.NewLine,””) in foreach line. However to get URL parsed you still need a space at the end. For example to get “http://google.com” parsed, you have to enter “http://google.com ” to tell that URL already ended.

    foreach (Match match in regexUrl.Matches(content))
    ...
    

Leave a Reply

Your email address will not be published. Required fields are marked *