This article is compatible with the latest version of Silverlight.
In this article, we’ll take a look at the Silverlight Rich Text Box control. The Rich Text Box was one of the new, and highly requested controls introduced in Silverlight 4.
This article is accompanied by a Visual Studio solution, which you can find here.
What do we get?
Out of the box, the Rich Text Box control looks like a simple TextBox: you get the typical TextBox look, nothing more. However, under the covers, it offers us the ability to display formatted text, paragraphs, hyperlinks or inline images – and even more than that: you can actually use the Rich Text Box to display any UIElement (so if you want a DataGrid in your Rich Text Box, that’s entirely possible).
The Rich Text Box has a content property, Blocks, which is a collection of Paragraph elements (Paragraph derives from Block). These Paragraph elements can in turn contain elements that are derived from Inline, like Run (we know that one from the regular Text Box), Span, Bold, Italic, Underline, Hyperlink and the InlineUIContainer (which can contain UIElements).
This image (from MSDN) offers an overview of the content model:
As you can see in this image, Inline elements (excluding Hyperlink) can contain other Inline elements. Looking at the above image, you can already guess we’re dealing with a really powerful control.
We’ll run through each of these possibilities in this article, and we’ll also look at a few more options the Rich Text Box offers. We’ll end up with a Silverlight project looking like this:
But let’s start by adding the control itself to our view.
Adding a Rich Text Box control
As with all XAML controls, this is pretty simple: the Rich Text Box control is contained in the System.Windows.Controls namespace, in the System.Windows assembly. To use it, simply add the following XAML syntax to your View (no imports are needed):
<RichTextBox AcceptsReturn="True"
Margin="5"
x:Name="rbtMyRichTextBox">
</RichTextBox>
Easy enough. The two most commonly used properties of the Rich Text Box are probably AcceptReturn (we’ve put this to True) and IsReadOnly. It’s only in read-only mode that UI elements and Hyperlinks are active (you can click the Hyperlink or actively use the other UI elements).
Adding elements
As a Rich Text Box consists of Paragraphs, which consists of Inline elements, we can easily add each and any type of Inline element we like. First, we’ll add a few buttons to our UI to add content to the control. In the button handlers, we’ll create the content. In the included source code for this article, you can find code to add various kinds of elements, and we’ll have a look at combining a few elements into one Paragraph element.
To add some text with a hyperlink, what we need to do is create a new Paragraph. To this Paragraph, we’ll add the other elements we need: the plain text, and the Hyperlink. These objects can be added to the Inlines collection of the Paragraph. In the end, we add the Paragraph to the Blocks collection of the Rich Text Box:
private void AddTextAndLink_Click(object sender, RoutedEventArgs e)
{
// create a paragraph
Paragraph prgParagraph = new Paragraph();
// create some text, and add it to the paragraph
Run rnMyText = new Run();
rnMyText.Text = "This is some example text with a ";
prgParagraph.Inlines.Add(rnMyText);
// create a link, and add it
Hyperlink lnkSSLink = new Hyperlink();
lnkSSLink.Inlines.Add("link to Silverlight Show");
lnkSSLink.NavigateUri = new Uri("http://www.silverlightshow.net");
prgParagraph.Inlines.Add(lnkSSLink);
// add the paragraph to the RTB
rbtMyRichTextBox.Blocks.Add(prgParagraph);
}
Run can only contain unformatted text. To format this text, we can include the Run element in the Inline collection of one of the “format” Inlines, like Bold, Italic, … All elements in the Inline collection of, for example, Bold, will be formatted with a heavier weight. And what’s more: these elements can be combined, so we can create bold, italic text:
private void AddFormattedText_Click(object sender, RoutedEventArgs e)
{
// create a paragraph
Paragraph prgParagraph = new Paragraph();
// create some text, and add it to the paragraph
Bold bldText = new Bold();
bldText.Inlines.Add(new Run() { Text = "This is some example text in bold" });
Italic itlText = new Italic();
itlText.Inlines.Add(new Run() { Text = "This is some example text in italics" });
Underline unText = new Underline();
unText.Inlines.Add(new Run() { Text = "This is some example text, underlined" });
Bold bldTextWithItalic = new Bold();
bldTextWithItalic.Inlines.Add(new Italic() { Inlines = { new Run()
{ Text = "This is some example text, bold and italic" } } });
prgParagraph.Inlines.Add(bldText);
prgParagraph.Inlines.Add(new LineBreak());
prgParagraph.Inlines.Add(itlText);
prgParagraph.Inlines.Add(new LineBreak());
prgParagraph.Inlines.Add(unText);
prgParagraph.Inlines.Add(new LineBreak());
prgParagraph.Inlines.Add(bldTextWithItalic);
rbtMyRichTextBox.Blocks.Add(prgParagraph);
}
But what if you want to add, say, an Image or even a DataGrid to your Rich Text Box? To this avail, the InlineUIContainer exists, which acts as a container for any UIElement. The same principles apply as with other Inline elements: create an InlineUIContainer, add your UIElement to it, add the container to a Paragraph, and at the Paragraph to the RichTextBox:
private void AddDataGrid_Click(object sender, RoutedEventArgs e)
{
InlineUIContainer iuicContainer = new InlineUIContainer();
DataGrid dtgGrid = new DataGrid();
dtgGrid.AutoGenerateColumns = true;
dtgGrid.Width = 400;
dtgGrid.Height = 200;
dtgGrid.ItemsSource = CreatePersonList();
iuicContainer.Child = dtgGrid;
Paragraph prgParagraph = new Paragraph();
prgParagraph.Inlines.Add(iuicContainer);
rbtMyRichTextBox.Blocks.Add(prgParagraph);
}
Of course, all these elements can be added through XAML instead of through code. For example, adding an image to our Rich Text Box with XAML code could be achieved like this:
<RichTextBox AcceptsReturn="True"
Margin="5"
x:Name="rbtMyRichTextBox">
<Paragraph>
<InlineUIContainer>
<Image Source="Assets/logo.png" Width="100"></Image>
</InlineUIContainer>
</Paragraph>
</RichTextBox>
What about manipulating a selection?
Up until now, we’ve programmatically added elements to our Rich Text Box control (and of course you can just type in it), but this is not the typical behavior a user expects from a control like this: he wants to be able to select text and manipulate it any way he wants (eg: making the text bold). Selecting text is possible out of the box, and the Rich Text Box control has a Selection property of type TextSelection, which contains the currently selected text. We can manipulate this selection through the GetPropertyValue and ApplyPropertyValue methods: we get the FontWeight property value, check if it’s normal or bold, and change the value accordingly.
Afterwards, we put the focus back on the Rich Text Box.
private void MakeBold_Click(object sender, RoutedEventArgs e)
{
// change text
if (rbtMyRichTextBox != null && rbtMyRichTextBox.Selection.Text.Length > 0)
{
if (rbtMyRichTextBox.Selection.GetPropertyValue(Run.FontWeightProperty) is FontWeight
&& ((FontWeight)rbtMyRichTextBox.Selection.GetPropertyValue(Run.FontWeightProperty))
== FontWeights.Normal)
{
rbtMyRichTextBox.Selection.ApplyPropertyValue(Run.FontWeightProperty, FontWeights.Bold);
}
else
{
rbtMyRichTextBox.Selection.ApplyPropertyValue(Run.FontWeightProperty, FontWeights.Normal);
}
}
// return focus
if (rbtMyRichTextBox != null)
{
rbtMyRichTextBox.Focus();
}
}
Important to know is that you can only change property values like this if your selection extends over text with only one value for the formatting property – if not, UnsetValue is returned.
By applying the same logic, you can manipulate other text properties, like font size, font family, …
I want to get some copy & paste functionality.
What’s a Rich Text Box control without copy and paste functionality? It’s something users expect, and thanks to the fact that we can access the clipboard from Silverlight (through System.Windows.Clipboard), it’s pretty easy to implement: the selected text is in the Selection property of the Rich Text Box, and can be added to the clipboard as such (for copy and cut):
private void Copy_Click(object sender, RoutedEventArgs e)
{
Clipboard.SetText(rbtMyRichTextBox.Selection.Text);
rbtMyRichTextBox.Focus();
}
private void Cut_Click(object sender, RoutedEventArgs e)
{
Clipboard.SetText(rbtMyRichTextBox.Selection.Text);
rbtMyRichTextBox.Selection.Text = "";
rbtMyRichTextBox.Focus();
}
To paste our text, we should just set the Selection property of the Rich Text Box to the text from the clipboard. This will either overwrite the selection, or, if nothing is selected, insert the text at the position of the cursor:
private void Paste_Click(object sender, RoutedEventArgs e)
{
rbtMyRichTextBox.Selection.Text = Clipboard.GetText();
rbtMyRichTextBox.Focus();
}
After each operation, we should of course return the focus to the Rich Text Box control.
Important to know is that you will lose formatting when using copy/paste, because the Silverlight Clipboard can only contain simple text.
Ok, so I’ve created a document. How do I save it?
The easiest way to save the contents of a Rich Text Box is by persisting the underlying XAML syntax of the Paragraph, Inline, … elements to a file. The Rich Text Box has a property through which you can easily access the underlying XAML code: Xaml.
What we do not want to save are the InlineUIContainer elements as this is not supported, so we check for this. If there are none, we open a Save File Dialog, and use a FileStream to write the XAML to the file:
private void Save_Click(object sender, RoutedEventArgs e)
{
// we do not save UI Elements
var uiElements = from block in rbtMyRichTextBox.Blocks
from inline in (block as Paragraph).Inlines
where inline.GetType() == typeof(InlineUIContainer)
select inline;
if (uiElements.Count() != 0)
{
MessageBox.Show("UIElements cannot be saved");
return;
}
SaveFileDialog sfdSaveXaml = new SaveFileDialog();
sfdSaveXaml.DefaultExt = ".sav";
sfdSaveXaml.Filter = "Saved rtb files|*.rtb";
if (sfdSaveXaml.ShowDialog().Value)
{
using (FileStream fs = (FileStream)sfdSaveXaml.OpenFile())
{
System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
// the Xaml is what we want to save
byte[] buffer = enc.GetBytes(rbtMyRichTextBox.Xaml);
fs.Write(buffer, 0, buffer.Length);
fs.Close();
}
}
}
Opening a file is easy as well: just read all the XAML back and set the .Xaml property of your Rich Text Box to this code.
private void Open_Click(object sender, RoutedEventArgs e)
{
// open file
OpenFileDialog ofdOpenXaml = new OpenFileDialog();
ofdOpenXaml.Multiselect = false;
ofdOpenXaml.Filter = "Saved rtb files|*.rtb";
if (ofdOpenXaml.ShowDialog().Value)
{
FileInfo fiXamlFile = ofdOpenXaml.File;
StreamReader sr = fiXamlFile.OpenText();
rbtMyRichTextBox.Xaml = sr.ReadToEnd();
sr.Close();
}
}
Can we save to other file formats, and open them as well?
The short answer is: yes, you can. The long answer: yes, you can, but it will require some extra coding on your part. The Rich Text Box expects XAML code, so if you want to open, say, a txt file, you’ll have to parse the text file yourself and create the necessary Run elements . The same logic applies for opening a basic xml file, a docx file, … you can do this, but: you’ll have to write functions to parse the contents of the file and convert it to XAML, which can then be applied to the Rich Text Box.
Saving the file comes down to getting the XAML from your Rich Text Box, and converting it to the file format you want to save it to. You could look at the source code of the Word to XAML project over at Codeplex (this is a Word plugin, but you can get some inspiration on parsing docx content from it; there are others as well, both open source and commercial) to get an idea of how to start with this – but keep in mind that the more complex the file format you want to save to is, the more work it will cost you. A docx probably isn’t the best file format to start with, as they can quickly become quite complex.
Conclusion
In this article, we’ve looked into the possibilities of the Silverlight Rich Text Box control. We’ve learned what types of content the Rich Text Box control can contain, how we can add them and how we can manipulate them. We’ve also looked into adding the ability to copy and paste text, and finally enabled loading and saving our edited files.
About the author
Kevin Dockx lives in Belgium and works at RealDolmen, one of Belgium's biggest ICT companies, where he is a technical specialist/project leader on .NET web applications, mainly Silverlight, and a solution manager for Rich Applications (Silverlight, Windows Phone 7 Series, WPF, Surface). His main focus lies on all things Silverlight, but he still keeps an eye on the new developments concerning other products from the Microsoft .NET (Web) Stack. As a Silverlight enthusiast, he's a regular speaker on various national and international events, like Microsoft DevDays in The Netherlands, Microsoft Techdays in Portugal or on BESUG events (the Belgian Silverlight User Group). Next to that, he also authored a best-selling Silverlight book, Packt Publishing's Silverlight 4 Data and Services Cookbook, together with Gill Cleeren. His blog, which contains various tidbits on Silverlight, .NET, and the occasional rambling, can be found at http://blog.kevindockx.com/.