Struggling to find a clear WPF (not Forms) example of finding cursor position of searched text and then scrolling text into view using a RichEditBox.
DocumentViewer has the desired text search ability but is read only and I require write.
RichTextBox uses a FlowDocument to represent images, formatting... in addition to text. The catch is TextRange.Text.IndexOf(string, start) returns the index within text, not the FlowDocument's TextPointer position!
To get the TextPointer requires indexing through FlowDocument. But this is very slow. By checking each FlowDocument Block for the searched for string and only indexing through this block significantly improves search time.
private static bool SearchInRichTextBox(RichTextBox rtb, string searchFor, StringComparison stringComparison)
{
string searchForComparison = MatchStringComparison(searchFor, stringComparison); // Match searchFor to StringComparison
TextRange searchRange = new(rtb.Document.ContentStart, rtb.Document.ContentEnd);
foreach (Block block in rtb.Document.Blocks)
{
searchRange.Select(block.ContentStart, block.ContentEnd);
if (searchRange.Text.Contains(searchForComparison, stringComparison))
{
if (FindTextInRange(searchRange, searchForComparison) is TextRange textRange)
{
textRange.ApplyPropertyValue(TextElement.FontWeightProperty, FontWeights.Bold);
textRange.ApplyPropertyValue(TextElement.FontSizeProperty, 20.0);
textRange.ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.Green); // TextElement required for BackgroundProperty.
textRange.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Red); // TextElement not required for ForegroundProperty?
Rect startCharRect = textRange.Start.GetCharacterRect(LogicalDirection.Forward);
// Attempt to scroll searchForComparison into midpoint (rtb.ActualHeight / 2.0) of view
rtb.ScrollToVerticalOffset(startCharRect.Top - rtb.ActualHeight / 2.0);
}
return true;
}
}
return false;
}
For StringComparison that include IgnoreCase ensure searched for string is lower case.
private static string MatchStringComparison(string searchFor, StringComparison stringComparison)
{
string compare;
switch (stringComparison)
{
case StringComparison.Ordinal:
case StringComparison.CurrentCulture:
case StringComparison.InvariantCulture:
compare = searchFor;
break;
case StringComparison.OrdinalIgnoreCase:
case StringComparison.CurrentCultureIgnoreCase:
case StringComparison.InvariantCultureIgnoreCase:
compare = searchFor.ToLower();
break;
default: throw new ArgumentException("Unknown StringComparison");
}
return compare;
}
Find the TextRange by indexing through the searchRange known to contain the searched for text.
private static TextRange? FindTextInRange(TextRange searchRange, string searchText)
{
// The start position of the search
TextPointer current = searchRange.Start.GetInsertionPosition(LogicalDirection.Forward);
while (current != null)
{
// The TextRange that contains the current character
TextRange text = new(current.GetPositionAtOffset(0, LogicalDirection.Forward),
current.GetPositionAtOffset(1, LogicalDirection.Forward));
// If the current character is the start of the searched searchFor
if (text.Text == searchText[0].ToString())
{
TextRange match = new(current, current.GetPositionAtOffset(searchText.Length, LogicalDirection.Forward));
if (match.Text == searchText)
{
// Return the match
return match;
}
}
// Move to the next character
current = current.GetPositionAtOffset(1, LogicalDirection.Forward);
}
// Return null if no match found
return null;
}
Html for RichTextBox
<!-- Must place in grid cell for scroll bars to function -->
<RichTextBox x:Name="rtb"
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
SpellCheck.IsEnabled="True"/>