private static void OnXamlStringChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { TextBlock tb = d as TextBlock; if (tb == null) { return; } string xamlString = e.NewValue as string; if (string.IsNullOrEmpty(xamlString)) { return; } InlineCollection inlineCollection = CreateInlineCollection(xamlString); if (inlineCollection == null || inlineCollection.Count == 0) { return; } Inline[] inlines = new Inline[inlineCollection.Count]; inlineCollection.CopyTo(inlines, 0); PopulateTextBlockInlines(tb, inlines); }
/// <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) { var 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. var start = wordStartPosition.GetPositionAtOffset(0, LogicalDirection.Backward); var end = caretPosition.GetPositionAtOffset(0, LogicalDirection.Forward); if (start != null) { if (end != null) { new Hyperlink(start , end); } } // 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; var 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]); if (hyperlink.SiblingInlines != null) { 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(); var 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. if (hyperlink.SiblingInlines != null) { hyperlink.SiblingInlines.Remove(hyperlink); } // 5. Update selection, since we deleted Hyperlink element and caretPosition was at that Hyperlink's end boundary. if (newCaretPosition != null) { myRichTextBox.Selection.Select(newCaretPosition, newCaretPosition); } } } }
private void RemoveHyperlinkFormat() { TextPointer caretPosition = Selection.Start; TextPointer backspacePosition = caretPosition.GetNextInsertionPosition(LogicalDirection.Backward); Hyperlink hyperlink = default(Hyperlink); try { if (backspacePosition != null && IsHyperlinkBoundaryCrossed(caretPosition, backspacePosition, ref hyperlink)) { // Remember caretPosition with forward gravity. This is necessary since we are going to delete // the hyperlink element preceding caretPosition and after deletion current caretPosition // (with backward gravity) will follow content preceding the hyperlink. // We want to remember content following the hyperlink to set new caret position at. TextPointer newCaretPosition = caretPosition.GetPositionAtOffset(0, LogicalDirection.Forward); // 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]); if (hyperlink.SiblingInlines != null) { hyperlink.SiblingInlines.InsertAfter(hyperlink, inlines[i]); } } // 3. Apply hyperlink 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 dependencyProperty = property.Property; object value = property.Value; // Ignore hyperlink defaults if (dependencyProperty.ReadOnly == false && dependencyProperty.Equals(Inline.TextDecorationsProperty) == false && dependencyProperty.Equals(TextElement.ForegroundProperty) == false && dependencyProperty.Equals(BaseUriHelper.BaseUriProperty) == false && IsHyperlinkProperty(dependencyProperty) == false && dependencyProperty.Name.Equals("IsEnabled") == false) { inlineRange.ApplyPropertyValue(dependencyProperty, value); } } // 4. Delete the (empty) hyperlink element if (hyperlink.SiblingInlines != null) { hyperlink.RequestNavigate -= OnRequestNavigate; hyperlink.SiblingInlines.Remove(hyperlink); } // 5. Update selection, since we deleted Hyperlink element and caretPosition was at that hyperlink's end boundary Selection.Select(newCaretPosition, newCaretPosition); } } catch (Exception ex) { Log.Error("Error while removing hyperlink format: {EX}", ex); } }