Xaml is really powerful and you can build nearly any user interface you want with it.
However it can be hard sometimes and in this post I will describe how you can create something I first thought it will be easy : a wrapping text with hyperlinks in it.
Something like this :
As you will read, this is not as straightforward as I thought.
I tried a few things before to make it works the way I want to.
#fail 1 : use hyperlink element in the Textblock
Did you remember Silverlight (:p) ? I do and especially the Hyperlink element that you can use in Textblock :
[xml]
<TextBlock IsReadOnly="True">
<Run> Displaying text with</Run>
<Hyperlink NavigateUri="http://www.infinitesquare.com"
TargetName="_blank">hyperlink</Hyperlink>
<Run>.</Run>
</TextBlock >
[/xml]
It’s really nice but…. there is no hyperlink element in the Windows8 Xaml framework… too bad 🙁
#fail 2 : use the tapped event on run elements
There is in fact no such thing in the Windows 8 Xaml Framework so this is a dead end 🙁
#fail 3 : use the textblock’s tapped event to retrieve the aimed run element
My third idea was to create a textblock with runs and Underline element to recreate the same display.
Then I can register myself to the Tapped event and grab the tapped run and then open the matching links.
I would then create something like this :
[xml]
<TextBlock>
<Run Text="Cupcake ipsum dolor. Sit amet I love croissant fawork" />
<Run Text=" " />
<Underline FontFamily="Segoe UI">
<Run Foreground="#FFFF1BF5"
Text="I love ice cream" />
</Underline>
<Run Text=". Chocolate ice cream soufflé pastry." />
<Run Text=" Bear claw chocolate tart brownie apple pie." />
<LineBreak />
<LineBreak />
<Run Text="Pastry sesame snaps cotton candy jelly-o marzipan pastry" />
<Run Text=" cake I love faworki. Wypas I love jelly." />
<Underline FontFamily="Segoe UI">
<Run Foreground="#FFFF1BF5"
Text="I love toffee macaroon chocolate bar." />
</Underline>
</TextBlock>
[/xml]
[csharp]
private void MyTexBlock_OnTapped(object sender, TappedRoutedEventArgs e)
{
var run = e.OriginalSource;
}
[/csharp]
The problem here is that the OriginalSource element is in fact the TextBlock itself and not the Run or the Underline element I created. So there is no way to know which link the user touched but only it touched the TextBlock somewhere.
#fail 4 : use a RichTextBlock with InlineUIContainer and an HyperlinkButton element
After these failures, I decided to use a weapon of mass destruction : a RichTextBlock and a InlineUIContainer element.
You can’t just add Run inside a RichTextBlock but instead, you have to create one (or more) root paragraph element.
The InlineUIContainer lets you put any FrameworkElement you want inside a document(the content of the Paragraph). You can then register handlers to the events of this FrameworkElements as you would have in any Xaml UI.
In the code snippet below, I register myself to Tapped event of the Textblock I insert in my paragraph :
[xml]
<RichTextBlock IsTextSelectionEnabled="False">
<Paragraph>
<Run Text="Cupcake ipsum dolor. Sit amet I love croissant fawork" />
<Run Text=". " />
<InlineUIContainer>
<Border Background="#FFF1EFEF">
<TextBlock Tapped="OnLinkTapped" Foreground="#FFFF1BF5"
TextWrapping="Wrap"> <Underline>
<Run Text="I love ice cream, to write very long text " />
<Run Text="and to take some screenshots." />
</Underline>
</TextBlock>
</Border>
</InlineUIContainer>
<Run Text="A Chocolate ice cream soufflé pastry. " />
<Run Text="Bear claw chocolate tart brownie apple pie." />
</Paragraph>
</RichTextBlock>
[/xml]
Wonderful… wait … oh no… there is still one issue : the paragraph element will consider the InlineUIContainer as “whole block” and not as “text” and then it will wrap strangely your content. In this screenshot, I added a gray border around my TextBlock show this behavior to you :
#Success : use a RichTextBlock and the GetPositionFromPoint method
I finally found one hack and if you know a better way, please tell me in the comment which one it is 🙂
My solution is to use the RichTextBlock’s tapped event and its GetPositionFromPoint method. This method returns a TextPointer for a given position of your RichTextBlock. This TextPointer object has a Parent property which is the TextElement (Run, Underline, Span, etc…) the user clicked on : exactly what we want !
My Xaml then look like this :
[xml]<RichTextBlock IsTextSelectionEnabled="False"
Margin="30"
Width="600"
TextAlignment="Justify"
FontSize="40"
TextWrapping="Wrap"
Tapped="UIElement_OnTapped">
<Paragraph>
<Run Text="Cupcake ipsum dolor. Sit amet I love croissant fawork" />
<Run Text=". " />
<Underline x:Name="LinkToInfiniteSquare"
Foreground="#FFFF1BF5">
<Run Text="I love ice cream, to write very long text " />
<Run Text="and to take some screenshots." />
</Underline>
<Run Text="A Chocolate ice cream soufflé pastry. " />
<Underline x:Name="LinkToMyBlog"
Foreground="#FFFF1BF5">
<Run Text="Bear claw chocolate tart brownie apple pie." />
</Underline>
</Paragraph>
</RichTextBlock>
[/xml]
The last part is to walk trough the parents of the clicked/touched element to find out its Underline parent, read its name and launch the correct action.
[csharp]
private void UIElement_OnTapped(object sender, TappedRoutedEventArgs e)
{
var richTB = sender as RichTextBlock;
var textPointer = richTB.GetPositionFromPoint(e.GetPosition(richTB));
var element = textPointer.Parent as TextElement;
while (element != null && !(element is Underline))
{
if (element.ContentStart != null
&& element != element.ElementStart.Parent)
{
element = element.ElementStart.Parent as TextElement;
}
else
{
element = null;
}
}
if (element == null) return;
var underline = element as Underline;
if (underline.Name == "LinkToInfiniteSquare")
{
Launcher.LaunchUriAsync(new Uri("http://www.infinitesquare.com"));
}
else if (underline.Name == "LinkToMyBlog")
{
Launcher.LaunchUriAsync(new Uri("http://www.jonathanantoine.com"));
}
}
[/csharp]
By the way, I can not use a TextBlock element because it does not have the GetPositionFromPoint method.
Do you know a better way ?
(Please don’t tell me to do it in HTML/JS :p)
The source code is available here.
08/06/2013 15 h 58 min
Hi!
How to use this together with binding?
I want to create a Twitter application (WinRT) which make potential links clickable, but I’ve yet to find a good way to do this.
08/06/2013 17 h 11 min
Hi. I would create/use a behavior/attached property to do this. see my post on the ramora pattern for more details on this pattern 🙂
07/10/2013 15 h 47 min
How to automatic detect hyperlinks in textblock?
11/12/2013 10 h 45 min
Tapped event not working when I use RichTextColumns within ScrollViewer. What's the solution?
28/04/2014 14 h 30 min
Thanks man. Really helpful post. This work also with resources dictionary.
24/07/2014 16 h 45 min
I want to create a Twitter application (WinRT) which make potential links clickable, but I've yet to find a good way to do this.
26/10/2014 19 h 29 min
Very good article
09/12/2014 20 h 01 min
Perfect.. this might help with a project I'm current work (came here looking for a way to change text background color using a TextPointer)
17/10/2015 6 h 18 min
I'm working on windows 10 and it looks like the <RichTextBox> together with its inline elements are the way to go. Supported elements include:
Paragraph
Run
LineBreak
Span
Bold
Hyperlink
Underline
Italic
See https://msdn.microsoft.com/en-us/library/ee681613….
21/08/2016 23 h 53 min
Awesome. Thanks for sharing!