Diederik Krols

The XAML Brewer

An auto-hyperlinking RichTextBlock for Windows 8.1 Store apps

This article shows how to let a XAML RichTextBlock control detect URLs and email addresses in its raw text, and show these as hyperlinks. The code only applies to Windows 8.1 because I’m using the brand new Hyperlink control. Here’s a screenshot of the attached sample app. There’s a regular textbox on top in which you can type whatever you like, the ‘Assign’ button sends your text to the pimped RichTextBlock at the bottom. This will transform all URLs and email addresses from the raw text into an operational hyperlink:

Unfortunately, the RichTextBlock control lives in a sealed class, so putting the logic in a child class was not an option. So I decided to go for an attached property called “RawText”:

/// <summary>
/// Ads auto-hyperlinking to a RichTextBlock control.
/// </summary>
public static class HyperlinkExtensions
{
    /// <summary>
    /// The raw text property.
    /// </summary>
    public static readonly DependencyProperty RawTextProperty =
        DependencyProperty.RegisterAttached("RawText", typeof(string), typeof(RichTextBlock), new PropertyMetadata("", OnRawTextChanged));

    /// <summary>
    /// Gets the raw text.
    /// </summary>
    public static string GetRawText(DependencyObject obj)
    {
        return (string)obj.GetValue(RawTextProperty);
    }

    /// <summary>
    /// Sets the raw text.
    /// </summary>
    public static void SetRawText(DependencyObject obj, string value)
    {
        obj.SetValue(RawTextProperty, value);
    }

    /// <summary>
    /// Called when raw text changed.
    /// </summary>
    private static void OnRawTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // Transformation logic ...
    }
}

In that same class I also defined an extension method that allows you to easily assign the raw text to the control:

/// <summary>
/// Sets the raw text.
/// </summary>
/// <remarks>Extension method.</remarks>
public static void AssignRawText(this RichTextBlock rtb, string value)
{
    rtb.SetValue(RawTextProperty, value);
}

Strangely, I could not name this extension method SetRawText(), because the C# compiler considered it a duplicate of the setter for the attached property. [Yes, I realize that it *is* a duplicate. But in Windows 8.0 I could get away with this, and that makes it ‘strangely’...]

You can assign the property in C#:

this.OutputBox.AssignRawText(this.InputBox.Text);

... or in XAML:

<RichTextBlock x:Name="OutputBox" hyper:HyperlinkExtensions.RawText="Your raw text here." />

The ‘hyperlinkables’ are detected by a regular expression that I found over here. The URLs and email addresses are added as Hyperlink to the Inlines collection of a Paragraph in the Block collection of the RichTextBlock. The rest of the raw text is added as plain Run instances.

What I like about the regex is that it also detects the URLs without a protocol (like www.u2uconsult.com). These look nice in the text, but for the NavigateUri property of the Hyperlink, it’s better to add the protocol.

Here’s the full transformation logic:

/// <summary>
/// Called when raw text changed.
/// </summary>
private static void OnRawTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    RichTextBlock rtb = (RichTextBlock)d;
    if (rtb != null)
    {
        // Working variables.
        string result = string.Empty;
        int index = 0;
        Paragraph par = new Paragraph();
        string text;
        Run run;
        Hyperlink link;
        string source = e.NewValue.ToString();

        // Regex initialization.
        string pattern = @"((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\jQuery15206922996124803609_1383046336084(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)";
        Regex regex = new Regex(pattern);
        var matches = regex.Matches(source);

        foreach (Match match in matches)
        {
            // Add text before match.
            int matchIndex = match.Index;
            text = source.Substring(index, matchIndex - index);
            run = new Run();
            run.Text = text;
            par.Inlines.Add(run);

            // Add match as hyperlink.
            string hyper = match.Value;
            link = new Hyperlink();
            run = new Run();
            run.Text = hyper;
            link.Inlines.Add(run);

            // Complete link if necessary.
            if (!hyper.Contains("@") && !hyper.StartsWith("http"))
            {
                hyper = @"http://" + hyper;
            }

            if (hyper.Contains("@") && !hyper.StartsWith("mailto"))
            {
                hyper = @"mailto://" + hyper;
            }
                    
            link.NavigateUri = new Uri(hyper);
            par.Inlines.Add(link);

            index = matchIndex + match.Length;
        }

        // Add text after last match.
        text = source.Substring(index, source.Length - index);
        run = new Run();
        run.Text = text;
        par.Inlines.Add(run);

        // Update RichTextBlock content.
        rtb.Blocks.Clear();
        rtb.Blocks.Add(par);
    }
}

Here’s how the sample app looks like when I tapped the URL link:


... and the e-mail address:

Here’s the sample app. It was written in the VS2013 preview for the Windows 8.1 preview: U2UC.WinRT.HyperlinkBox.zip (148.67 kb)

Enjoy!
Diederik

Comments (2) -

  • Manie Jergens

    2/7/2014 12:20:27 AM |

    hey there, your site is cheap. We do thank you for work

  • Ruben

    4/2/2014 8:17:29 PM |

    Great post. However, the "jQuery15206922996124803609_1383046336084"-part of your regex caused problems when I tried it. I used the expression from the source you mentioned and it worked like a charm.

Comments are closed