Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF RichTextBox containing Hyperlinks

0.00/5 (No votes)
21 Jan 2017 2  
Creating a custom RichTextBox that supports Hyperlinks

Download sample

Introduction

When working with Web controls it is quite easy to add hyperlinks within a paragraph of text. But while using windows applications the same is not so straight forward. The article shows how a  WPF RichTextBox control can be modified to include hyperlinks in between normal text.

Image 1

Background

Some time back, A friend of mine was working on a Windows application which contained a lot of text content. Within the text content he needed certain words / ID's to be hyperlinks, So that a user could click on those words and navigate to a different page ( In this case a web page ) which could show more information related to the word.

When working with WPF text controls one can easily include Hyperlinks  using controls  using code similar to the Below

<TextBlock> 
    <Hyperlink NavigateUri="http://www.google.com" RequestNavigate="Hyperlink_RequestNavigate">Click here
     </Hyperlink>
</TextBlock>

But this approach may not work if you need hyperlinks within sentences or paragraphs of text that are dynamically bound to the view. Similar to #hashtags.

The Solution involves creating a custom RichTextBox control that supports this functionality.

Using the code

The basic component of this solution is the CustomRichTextBox control which is created by deriving from a normal RichTextBox control. This control will have all the properties of a RichTextBox control and an additional dependency property named "CustomText" to display the text content bound to the control.

C#
public class CustomRichTextBox : RichTextBox
    {

        #region CustomText Dependency Property

        public static readonly DependencyProperty CustomTextProperty = DependencyProperty.Register("CustomText", typeof(string), typeof(CustomRichTextBox),
       new PropertyMetadata(string.Empty, CustomTextChangedCallback), CustomTextValidateCallback);

        private static void CustomTextChangedCallback(
            DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            (obj as CustomRichTextBox).Document = GetCustomDocument(e.NewValue as string);
        }

        private static bool CustomTextValidateCallback(object value)
        {
            return value != null;
        }

        public string CustomText
        {
            get
            {
                return (string)GetValue(CustomTextProperty);
            }
            set
            {
                SetValue(CustomTextProperty, value);
            }
        } 

        #endregion


        public CustomRichTextBox()
        {

        }

        private static  FlowDocument GetCustomDocument(string Text)
        {
            FlowDocument document = new FlowDocument();
            Paragraph para = new Paragraph();
            para.Margin = new Thickness(0); // remove indent between paragraphs
            foreach (string word in Text.Split(' ').ToList())
            {
                //This condition could be replaced by the Regex
                if(word.StartsWith("#"))
                {
                    string linkName = word.Substring(1, word.Length - 1);
                    //linkURL can be changed based on some condition.
                    string linkURL = GetUrl(linkName);
                   

                    Hyperlink link = new Hyperlink();
                    link.IsEnabled = true;
                    link.Inlines.Add(linkName);
                    link.NavigateUri = new Uri(linkURL);
                    link.RequestNavigate += (sender, args) => Process.Start(args.Uri.ToString());
                    para.Inlines.Add(link);
                }
                else
                {
                    para.Inlines.Add(word);
                }
                para.Inlines.Add(" ");
            }
            document.Blocks.Add(para);
            return document;
        }

        //This method may contain any logic to return a Url based on a key string
        static string GetUrl(string key)
        {
            return string.Format(@"https://www.google.com/#q={0}", key);
        }

    }

The method GetCustomDocument(string Text) splits each word in the text bound to the control and if a '#' prefix is encountered in a word, it dynamically creates a Hyperlink and adds it to the flow document.

Once the CustomRichTextBox control is ready it can used in a view (xaml) and bound to a property with text content just as you would bind content with a text control.

XML
<local:CustomRichTextBox IsDocumentEnabled="True" IsReadOnly="True" CustomText="{Binding Details}" /> 

And That's it ..

For an input text below :

"Lorem Ipsum has been the industry's standard dummy #Ford text ever since the 1500s."

The control would display the text as follows

Image 2

A full usage of this control in a simple WPF application is illustrated in the project attached with this article. So you can download the code to see the control in action.

Points of Interest

I have kept the article and the sample project as simple as possible so that it can be easily understood and modified by a person even though new to WPF. The approach can be used to include controls other than Hyperlinks as well.

For those new to WPF here are few topics you should become familiar with once you understand the article and the code.

  • Overiew of a RichTextBox control
  • Creating WPF Custom Controls
  • Adding & using Dependency Properties
  • Usage of Data Templates
  • Simple MVVM Binding

History

Initial Version : 18 January 2017

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here