1

Trying to select and color a specific word in WPF Richtextbox but my method selects just first 5 letters of the word. Indexes 0,1 and 2 seems to be empty string although the first word in my rtb is "private" and there is no empty string before it.

What can be the cause of this problem?

public  void FormatRtbText(RichTextBox rtb)
    {
        int x, y;
        string str = "private";

        var text = new TextRange(rtb.Document.ContentStart, rtb.Document.ContentEnd).Text;

        x = text.IndexOf(str);
        y = x + str.Length;

        var range = new TextRange(rtb.Document.ContentStart.GetPositionAtOffset(x), rtb.Document.ContentStart.GetPositionAtOffset(y));
        range.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Red);

    }
ilcognito
  • 37
  • 7

2 Answers2

1

GetPositionAtOffset considers 3 things as symbols while calculating the offset:

An opening or closing tag for the TextElement element.

A UIElement element contained in an InlineUIContainer or BlockUIContainer. Note that such a UIElement is always counted as exactly one symbol; any additional content or elements contained by the UIElement are not counted as symbols.

A 16-bit Unicode character inside of a text Run element.

Here the first two symbols are the Paragraph and Run elements. Therefore your TextRange is two symbols behind what you want. This code should the the work. (What this code does is just skipping symbols until the next symbol is text.)

TextPointer start = rtb.Document.ContentStart;
while (start.GetPointerContext(LogicalDirection.Forward) != TextPointerContext.Text)
{
    start = start.GetNextContextPosition(LogicalDirection.Forward);
    if (start == null) return;
}
...
var range = new TextRange(start, start.GetPositionAtOffset(y));
Yusuf Tarık Günaydın
  • 3,016
  • 2
  • 27
  • 41
0

I found that the offsets returned by the wpf rtf box are practically worthless. They don't take into account the hidden characters that the textbox requires. Each new paragraph, image, etc in the box will add even more hidden chars that skew the offset.

Here's what I came up with to search for the match closed to the caret position.

  private TextRange FindText(string findText)
    {
      var fullText = DoGetAllText();
      if (string.IsNullOrEmpty(findText) || string.IsNullOrEmpty(fullText) || findText.Length > fullText.Length)
        return null;

      var textbox = GetTextbox();
      var leftPos = textbox.CaretPosition;
      var rightPos = textbox.CaretPosition;

      while (true)
      {
        var previous = leftPos.GetNextInsertionPosition(LogicalDirection.Backward);
        var next = rightPos.GetNextInsertionPosition(LogicalDirection.Forward);
        if (previous == null && next == null)
          return null; //can no longer move outward in either direction and text wasn't found

        if (previous != null)
          leftPos = previous;
        if (next != null)
          rightPos = next;

        var range = new TextRange(leftPos, rightPos);
        var offset = range.Text.IndexOf(findText, StringComparison.InvariantCultureIgnoreCase);
        if (offset < 0)
          continue; //text not found, continue to move outward

        //rtf has broken text indexes that often come up too low due to not considering hidden chars.  Increment up until we find the real position
        var findTextLower = findText.ToLower();
        var endOfDoc = textbox.Document.ContentEnd.GetNextInsertionPosition(LogicalDirection.Backward);
        for (var start = range.Start.GetPositionAtOffset(offset); start != endOfDoc; start = start.GetPositionAtOffset(1))
        {
          var result = new TextRange(start, start.GetPositionAtOffset(findText.Length));
          if (result.Text?.ToLower() == findTextLower)
          {
            return result;
          }
        }
      }
    }

If you want to highlight the match then it'd be as simple as changing this method to void and doing this when you found the match:

textbox.Selection.Select(result.Start, result.End);
Bill Tarbell
  • 4,933
  • 2
  • 32
  • 52