6

I'm trying to implement the Application.Find command for the WPF richtextbox. Let's say I'm searching for "expert". Sounds easy enough. But due to the nature of wpf, if every other letter in "expert" is bolded, then the richtextbox contains e*x*p*e*r*t* and that means six runs exist. I have a starting textPointer. What I'm trying to figure out is how to get the ending textPointer so that I can create a TextRange that I can use to create the Selection.

In this example, the starting textpointer is in the first run, and the ending textpointer should be in the last run. Is there a simple way to generate a textpointer if you know the run and the offset within the run? I tried generating it using a offset from the first textpointer but that did not work because the offset was not within the first run.

As a relative newbie to the WPF richtextbox, this one has me stumped. I imagine that this problem has already been tackled and solved. I did find one partial solution but it only worked on a single run and does not address the multiple run situation.

user1624542
  • 73
  • 1
  • 5

2 Answers2

14

The idea is to find the offset of the first character (IndexOf) and then to find the TextPointer at this index (but by counting only text characters).

public TextRange FindTextInRange(TextRange searchRange, string searchText)
{
    int offset = searchRange.Text.IndexOf(searchText, StringComparison.OrdinalIgnoreCase);
    if (offset < 0)
        return null;  // Not found

    var start = GetTextPositionAtOffset(searchRange.Start, offset);
    TextRange result = new TextRange(start, GetTextPositionAtOffset(start, searchText.Length));

    return result;
}

TextPointer GetTextPositionAtOffset(TextPointer position, int characterCount)
{
    while (position != null)
    {
        if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
        {
            int count = position.GetTextRunLength(LogicalDirection.Forward);
            if (characterCount <= count)
            {
                return position.GetPositionAtOffset(characterCount);
            }

            characterCount -= count;
        }

        TextPointer nextContextPosition = position.GetNextContextPosition(LogicalDirection.Forward);
        if (nextContextPosition == null)
            return position;

        position = nextContextPosition;
    }

    return position;
}

This is how to use the code:

TextRange searchRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
TextRange foundRange = FindTextInRange(searchRange, "expert");
foundRange.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.Red));
meziantou
  • 20,589
  • 7
  • 64
  • 83
  • Thank you so much meziatou. I will try this out now! – user1624542 Mar 08 '14 at 14:58
  • I was so lost in the weeds - this is 100 times better than the path I was taking. It works great. Thank you meziatou! I do need to tweak it to add an option to match whole word, but I think I can figure that out from here. – user1624542 Mar 08 '14 at 18:02
  • And sorry, apparently I don't have enough stackoverflow "reputation" to vote up, but I would if I could. – user1624542 Mar 08 '14 at 18:03
  • 7
    Good solution, but there is a minor problem. `GetTextRunLength` does not consider `\r` and `\n` characters. If you have those in `searchRange.Text` then the resulting TextRange will be ahead of the correct position by the number of new line characters. – Yusuf Tarık Günaydın May 13 '15 at 15:48
0

I found a more complete solution to be here on this GitHub page. https://github.com/manasmodak/WpfSearchAndHighlightText

It is able to deal with the \n and \r just fine and didn't have the errors I was dealing with from other solutions.

PWCoder
  • 93
  • 6