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

C# – Menu in NavigationWindow tutorial

NavigationWindow derives from System.Windows.Window and extends this class with ability for content navigation and navigation history. The content can be any type of .Net Framework and HTML sites. If you’re using XBAPs (www.xbap.org) you can realize that he uses IE as his kernel and NavigationWindow as his navigator. XBAP’s main window has reference to NavigationWindow and the navigation history is managed and integrated between NavigationWindow and IE to provide a better integrated user experience.
Building a NavigationWindow application is pretty simple. Just create a new project of WPF Form and then replace Window class to NavigationWindow as below

Old XAML

<Window x:Class="NavigationWindow.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"
...
</Window>

New XAML

<NavigationWindow x:Class="NavigationWindow.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"
...
</NavigationWindow>

Old C# Code

public partial class MainWindow : Window
{
	...
}

New C# Code

public partial class MainWindow : System.Windows.Navigation.NavigationWindow
{
	...
}

Then we have a window with a navigation bar on the top of window without writing any code. This window will contain all of our pages. Each page will present a step in a complete sequential workflow.

NavigationWindow

Now we add some pages so that we can navigate between them to demonstrate how navigator works. To keep it simple, I just add some very simple pages with some URI to show how we can jump to desired pages.

1. First Page

<Grid>
	<Grid.RowDefinitions>
		<RowDefinition Height="5*"/>
		<RowDefinition Height="5*" />
	</Grid.RowDefinitions>
		
	<TextBlock Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center" Text="I am the first page" />
	<TextBlock Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">
		Click <Hyperlink NavigateUri="SecondPage.xaml">here</Hyperlink> to go to Page 2
	</TextBlock>
</Grid>

First Page

2. Second Page

<Grid>
	<Grid.RowDefinitions>
		<RowDefinition Height="4*"/>
		<RowDefinition Height="3*" />
		<RowDefinition Height="3*" />
	</Grid.RowDefinitions>
	<TextBlock Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center" Text="I am the second page." />
	<TextBlock Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">
		Click <Hyperlink NavigateUri="FirstPage.xaml">here</Hyperlink> to go back
	</TextBlock>
	<TextBlock Grid.Row="2" HorizontalAlignment="Center" VerticalAlignment="Center">
		Click <Hyperlink NavigateUri="ThirdPage.xaml">here</Hyperlink> to go next
	</TextBlock>
</Grid>

Second Page

3.Third Page

<Grid>
	<Grid.RowDefinitions>
		<RowDefinition Height="4*"/>
		<RowDefinition Height="3*" />
		<RowDefinition Height="3*" />
	</Grid.RowDefinitions>
	<TextBlock Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center" Text="I am the third page." />
	<TextBlock Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">
		Click <Hyperlink NavigateUri="SecondPage.xaml">here</Hyperlink> to go back
	</TextBlock>
	<TextBlock Grid.Row="2" HorizontalAlignment="Center" VerticalAlignment="Center">
		Click <Hyperlink NavigateUri="FirstPage.xaml">here</Hyperlink> to go to first page
	</TextBlock>
</Grid>

Third Page

All pages are on the same level (on same folder) therefore I can call them directly with NavigateUri. The history was displayed exactly like in IE.

Navigation Url
If we want to add a menu available for all pages, just define one on the top of NavigatorForm.Content.

<NavigationWindow.Content>
	<Grid>
		<Menu x:Name="mnuTop" Height="25" VerticalAlignment="Top">
			<MenuItem Header="First" Command="{Binding MenuItemClick}" CommandParameter="FirstPage.xaml"/>
			<MenuItem Header="Second" Command="{Binding MenuItemClick}" CommandParameter="SecondPage.xaml" />
			<MenuItem Header="Third" Command="{Binding MenuItemClick}" CommandParameter="ThirdPage.xaml"/>
		</Menu>
		<Border BorderThickness="1" Margin="0,25,0,0" BorderBrush="Black">
			<Frame Source="{Binding CurrentSelectedPage, Mode=TwoWay}"/>
		</Border>
	</Grid>
</NavigationWindow.Content>

So with the NavigationWindow, we have a full supported navigator with content and history without “writing any source code” for that. Yes, not really “no code”, I am using MVVM.Light Framework to generate the code for me. The Frame.Source property is bound with a property of ViewModel. All MenuItems commands will be bound with a common action and when a menu item clicked, he’ll call this action with a given parameter in CommandParameter property. From the side of action, he need to be declared as a function with one input parameter type of object to receive value of this parameter.

private RelayCommand<object> menuItemClickCommand;
public ICommand MenuItemClick
{
	get
	{
		if (menuItemClickCommand == null)
			menuItemClickCommand = new RelayCommand<object>(o => HandleMenuItemClick(o));
		return menuItemClickCommand;
	}
}

private void HandleMenuItemClick(object e)
{
	CurrentSelectedPage = e.ToString();
}

NavigationWindow with Menu

There is only one notice that the F5 key is reserved for refresh the content of Frame in NavigationWindow.Content. Therefore don’t use this Key for any action in your application because it was hooked by NavigationWindow. This demo was written with MVVM.Light Framework and the source code can be downloaded here “NavigationWindow

Hacking – How was my Twitter account hacked?

On Saturday 06.08 I received an email of Twitter saying that my account was maybe hacked by someone. The content of email starts as below

Twitter believes that your account may have been compromised by a website or service
not associated with Twitter. We’ve reset your password to prevent others from
accessing your account….

I think that is again a spam trying to get my Twitter password. As usual I’ll delete it immediately but fortunately I look at the sender of the email and I’m pretty nervous because it’s real Twitter

From:  	"Twitter" <resetpwnotice-ebatpunhn=ebatpunhn.arg-def3a@postmaster.twitter.com>;

Continue reading Hacking – How was my Twitter account hacked?