This article is compatible with the latest version of Silverlight.
Introduction
In Silverlight there is a TextBlock control that is used to display simple text. However in many cases you need to display hyperlinks in the text. This article focuses on how to build such control and provides full source code for it.
* UPDATE *
Thanks to the feedback from Harlequin and his contribution now this project is published on CodePlex - http://www.codeplex.com/SilverlightLinkLabel. There you can download the source code as well as a sample project illustrating the functionality of the LinkLabel control.
Overview
In this article I take for granted that you have basic understandings how to build a custom control in Silverlight. I described the main flow in a previous post so go read it if you need to fill some gaps.
The implementation of LinkLabel control requires 3 main steps to be taken in consideration:
- Hyperlink recognition
- Text replacement
- Text and hyperlink arrangement
Hyperlink Recognition
First of all we need a mechanism to recognize or replace specific words in a given text - the words that will act as hyperlinks. We use two methods for this purpose:
- Match valid URIs - process the text and find valid URIs
- Match user defined hyperlink pattern - process the text and find a user defined hyperlink pattern, for example [link="URI"]text[/link]
The LinkLabel control can use either one of these methods or both in the same time.
Both methods can be implemented by using regular expressions. Lets take a look at the implementation of the second method:
private LinkCollection GetLinkMatches()
{
LinkCollection uriCollection = null;
Uri currentUri = null;
Regex uriLocator = new Regex( this.LinkPattern );
MatchCollection uriMatches = uriLocator.Matches( this.Text );
// no uris found
if ( uriMatches == null || uriMatches.Count == 0 )
{
return null;
}
foreach ( Match uri in uriMatches )
{
// not valid uri - continue with next match
if ( !Uri.TryCreate(
uri.Groups[ LinkPatternUriGroupName ].Value,
UriKind.RelativeOrAbsolute, out currentUri ) )
{
continue;
}
if ( uriCollection == null )
{
uriCollection = new LinkCollection();
}
uriCollection.Add( new Link( uri.Value, currentUri )
{
Text = uri.Groups[ LinkPatternTextGroupName ].Value
} );
}
return uriCollection;
}
To simplify the code and enable the user to define what should be the hyperlink text and the hyperlink navigate URI the regular expression defines text group and link group. This way it is easy to define custom hyperlink match pattern if you don't like the default one. In general the code above matches all hyperlinks (by user defined pattern) and put them in a LinkCollection - a custom generic collection of type Link. Link is a custom class used for basic link definition with a few properties - Key, NavigateUri, TargetName and Text.
Text Replacement
After we have the links the second step is to replace them in the text.
string linkLabelText = this.Text;
string preUri = string.Empty;
string[] preUriWords = null;
string postUri = string.Empty;
string[] postUriWords = null;
int startIndexOfUri = 0;
char[] delimiter = { ' ' };
// no uris found
if ( links == null || links.Count == 0 )
{
this.layoutRoot.Children.Add( new TextBlock()
{
Text = this.Text,
Style = this.TextStyle
} );
return;
}
foreach ( Link link in links )
{
startIndexOfUri = linkLabelText.IndexOf( link.Key, StringComparison.OrdinalIgnoreCase );
preUri = linkLabelText.Substring( 0, startIndexOfUri );
postUri = linkLabelText.Substring( preUri.Length + link.Key.Length );
linkLabelText = postUri;
// put all the words before the current Uri
preUriWords = preUri.Split( delimiter, StringSplitOptions.RemoveEmptyEntries );
foreach ( string preWord in preUriWords )
{
this.layoutRoot.Children.Add( new TextBlock()
{
Text = preWord + " ",
Style = this.TextStyle
} );
}
// insert the Uri
HyperlinkButton hyperlink = new HyperlinkButton()
{
Content = link.Text + " ",
NavigateUri = link.NavigateUri,
TargetName = link.TargetName,
Style = this.LinkStyle
};
hyperlink.Click += new RoutedEventHandler( this.ClickLink );
this.layoutRoot.Children.Add( hyperlink );
}
// append the text after the last uri found
if ( !string.IsNullOrEmpty( linkLabelText ) )
{
postUriWords = postUri.Split( delimiter, StringSplitOptions.RemoveEmptyEntries );
foreach ( string postWord in postUriWords )
{
this.layoutRoot.Children.Add( new TextBlock()
{
Text = postWord + " ",
Style = this.TextStyle
} );
}
}
The above code is the core of the LinkLabel control. The method takes a LinkCollection as an input parameter and iterates over it. Here is the process explained:
- For each link in the collection the text before its position is assigned to a local variable preUri.
- preUri is split by spaces and each word is set to the Text property of a new TextBlock control.
- The TextBlocks are added as child elements in the layoutRoot.
- A HyperlinkButton control is created and also added as a child element in the layoutRoot
This process continues until all hyperlinks replaced and added to the layoutRoot. Finally if there is any text left after the last hyperlink it is also added in the layoutRoot.
Text and Hyperlink Arrangement
Now it gets prettier. We have made the replacements in the text with the desired links and add all TextBlocks and HyperlinkButtons to the layoutRoot. What type of control actually is the layoutRoot? The answer is WrapPanel. By using a WrapPanel we can easily arrange the text and the hyperlinks so they don't exceed the container's area. When a control is added in the panel it is placed next to the other controls on the line. If the line size is not enough to contain the control it is automatically placed on the next line.
WrapPanel control is not included in the current version of the framework. Anyway there are a number of open source Silverlight WrapPanel controls you can use. A great article in CodeProject explains how to implement a WrapPanel by yourself and also provides a complete source code. The implementation is good, however it needs some customization (more specificly the MeasureOverride method) to make it work as a real WrapPanel. I won't get into this here but you can read this article to learn more on the topic.
Summary
It is so native to put hyperlinks into a text that I haven't even thought this can't be achieved with Silverlight out of the box. The implementation of the LinkLabel control is not complex at all and maybe this is the main reason it is not included in the framework. I didn't dive too much in the details but there is source code so you can take a look and understand it by yourself.
References
Creating a Silverlight Custom Control - The Basics
Silverlight WrapPanel
MeasureOverride method implementation for the WrapPanel