Auto-detecting Hyperlinks in RichTextBox - Part II

In my previous post, we looked at code that auto-formats hyperlink strings while typing. The remaining part was to scan words when content is pasted in RichTextBox to auto-format any hyperlink strings.

The approach to do this is quite straight forward as you can imagine. Listen to DataObject.Pasting event on RichTextBox, set a flag to remember the paste operation. Later, in the handler for TextChanged event (this would be fired after paste is handled by base RTB), scan for any matching words. The code in this solution builds upon my earlier post of navigating words in RichTextBox.

The main challenge in this problem is how to track the start and end of pasted content. Since TextChanged event does not supply any information about what content changed, I use a simple trick here. In the DataObject.Pasting event handler, I remember two TextPointer positions.

/// <summary>

/// Event handler for DataObject.Pasting event on this RichTextBox.

/// </summary>

private void DataObjectPastingEventHandler(object sender, DataObjectPastingEventArgs e)

{

this.wordsAddedFlag = true;

this.selectionStartPosition = this.Selection.Start;

this.selectionEndPosition = this.Selection.IsEmpty ?

this.Selection.End.GetPositionAtOffset(0, LogicalDirection.Forward) :

this.Selection.End;

// We don't handle the event here. Let the base RTB handle the paste operation.

// This will raise a TextChanged event, which we handle below to scan for any matching hyperlinks.

}

The gravities for both positions are subtly important. For non-empty selection, its start and end have backward and forward gravities respectively. But when selection is empty (you just have a caret), start and end are collapsed to a single TextPointer instance with backward gravity. In this case, we normalize end position with forward gravity, so that end TextPointer sticks to content following its position. This is important, because during paste, stuff is going to be inserted between selection start and end positions. So start and end must have backward and forward gravities respectively to cover pasted content.

OK, so much for that gravities business! This simple trick does the job. In TextChanged event handler, we scan for hyperlinks auto-formatting them.  

        /// <summary>

        /// Event handler for RichTextBox.TextChanged event.

        /// </summary>

        private void TextChangedEventHandler(object sender, TextChangedEventArgs e)

        {

            if (!this.wordsAddedFlag || this.Document == null)

            {

                return;

            }

            // Temporarily disable TextChanged event handler, since following code might insert Hyperlinks,

            // which will raise another TextChanged event.

            this.TextChanged -= this.TextChangedEventHandler;

            TextPointer navigator = this.selectionStartPosition;

            while (navigator != null && navigator.CompareTo(this.selectionEndPosition) <= 0)

            {

                TextRange wordRange = WordBreaker.GetWordRange(navigator);

                if (wordRange == null || wordRange.IsEmpty)

                {

                    // No more words in the document.

                    break;

                }

                string wordText = wordRange.Text;

                if (wordText == "www.microsoft.com" &&

                    !HyperlinkHelper.IsInHyperlinkScope(wordRange.Start) &&

                    !HyperlinkHelper.IsInHyperlinkScope(wordRange.End))

                {

                    Hyperlink hyperlink = new Hyperlink(wordRange.Start, wordRange.End);

  navigator = hyperlink.ElementEnd.GetNextInsertionPosition(LogicalDirection.Forward);

                }

                else

                {

                    navigator = wordRange.End.GetNextInsertionPosition(LogicalDirection.Forward);

                }

            }

            this.TextChanged += this.TextChangedEventHandler;

            this.wordsAddedFlag = false;

            this.selectionStartPosition = null;

            this.selectionEndPosition = null;

        }

 

This is all it takes to detect hyperlinks on paste. I have attached full code to this post.

RTB_Hyperlink_AutoDetect_Part_II.zip