public Window1() { InitializeComponent(); StackPanel stackPanel = new StackPanel(); MyRichTextBox richTextBox = new MyRichTextBox(); stackPanel.Children.Add(richTextBox); this.Title = "Custom RichTextBox that auto-detects hyperlink strings while typing"; this.Content = stackPanel; }
/// <summary> /// Event handler for KeyDown event to auto-detect hyperlinks on space, enter and backspace keys. /// </summary> private static void OnKeyDown(object sender, KeyEventArgs e) { MyRichTextBox myRichTextBox = (MyRichTextBox)sender; if (e.Key != Key.Space && e.Key != Key.Back && e.Key != Key.Return) { return; } if (!myRichTextBox.Selection.IsEmpty) { myRichTextBox.Selection.Text = String.Empty; } TextPointer caretPosition = myRichTextBox.Selection.Start; if (e.Key == Key.Space || e.Key == Key.Return) { TextPointer wordStartPosition; string word = GetPreceedingWordInParagraph(caretPosition, out wordStartPosition); if (word == "www.microsoft.com") // A real app would need a more sophisticated RegEx match expression for hyperlinks. { // Insert hyperlink element at word boundaries. new Hyperlink( wordStartPosition.GetPositionAtOffset(0, LogicalDirection.Backward), caretPosition.GetPositionAtOffset(0, LogicalDirection.Forward)); // No need to update RichTextBox caret position, // since we only inserted a Hyperlink ElementEnd following current caretPosition. // Subsequent handling of space input by base RichTextBox will update selection. } } else // Key.Back { TextPointer backspacePosition = caretPosition.GetNextInsertionPosition(LogicalDirection.Backward); Hyperlink hyperlink; if (backspacePosition != null && IsHyperlinkBoundaryCrossed(caretPosition, backspacePosition, out hyperlink)) { // Remember caretPosition with forward gravity. This is necessary since we are going to delete // the hyperlink element preceeding caretPosition and after deletion current caretPosition // (with backward gravity) will follow content preceeding the hyperlink. // We want to remember content following the hyperlink to set new caret position at. TextPointer newCaretPosition = caretPosition.GetPositionAtOffset(0, LogicalDirection.Forward); // Deleting the hyperlink is done using logic below. // 1. Copy its children Inline to a temporary array. InlineCollection hyperlinkChildren = hyperlink.Inlines; Inline[] inlines = new Inline[hyperlinkChildren.Count]; hyperlinkChildren.CopyTo(inlines, 0); // 2. Remove each child from parent hyperlink element and insert it after the hyperlink. for (int i = inlines.Length - 1; i >= 0; i--) { hyperlinkChildren.Remove(inlines[i]); hyperlink.SiblingInlines.InsertAfter(hyperlink, inlines[i]); } // 3. Apply hyperlink's local formatting properties to inlines (which are now outside hyperlink scope). LocalValueEnumerator localProperties = hyperlink.GetLocalValueEnumerator(); TextRange inlineRange = new TextRange(inlines[0].ContentStart, inlines[inlines.Length - 1].ContentEnd); while (localProperties.MoveNext()) { LocalValueEntry property = localProperties.Current; DependencyProperty dp = property.Property; object value = property.Value; if (!dp.ReadOnly && dp != Inline.TextDecorationsProperty && // Ignore hyperlink defaults. dp != TextElement.ForegroundProperty && !IsHyperlinkProperty(dp)) { inlineRange.ApplyPropertyValue(dp, value); } } // 4. Delete the (empty) hyperlink element. hyperlink.SiblingInlines.Remove(hyperlink); // 5. Update selection, since we deleted Hyperlink element and caretPosition was at that Hyperlink's end boundary. myRichTextBox.Selection.Select(newCaretPosition, newCaretPosition); } } }