I am developing test application where have RichTextBox
are and few buttons. That control contains text and whenever caret position is located when I am clicking button I want to count next 3 words from current caret position and after third word insert some text.
When I am trying to do this with WPF RichTextBox
it comes comlicated to do this. During investigation, I found RichTextBox
from WinForms namespace handle this task easier.
Does anyone know is there an extension, package for WPF RichTextBox that works in similar way as RichTextBox
from WinForms?
P.S. If there is no such solution, please share location where I can lear how to work with WPF RichTextBox
deeply.
Please, do not suggest to use RichTextBox
from Winforms in WPF
See the following InsertSomeText(this RichTextBox rtb, int skip, string text)
extension method below.
Example how to use it:
rtb.InsertSomeText(3, "<Text>");
The rtb
is a reference to the RichTextBox
control. The first parameter skip
defines how much words from the current caret position will be passed and then some text specified by the text
parameter will be inserted after the last word.
The regular expression is used to analyze a text. Currently the following characters is defined as word delimiters: ,
, .
, ;
, \s
(space) and
\t
(tab). This list can be updated depend on requirements. Additional information about this pattern you can see here: https://regexr.com/7c53c.
public static class RichTextBoxEx
{
public static void InsertSomeText(this RichTextBox rtb, int skip, string text)
{
var textRange = new TextRange(rtb.CaretPosition, rtb.Document.ContentEnd);
// Build list of Word/TextRange pairs
var words = CalculateTextRange(textRange, @"[^,.;\s\t]+", skip);
if (words.Count >= skip)
{
var required = words[skip - 1];
if (required.Item2 is TextRange tr && required.Item1 is string word)
{
var position = tr.Start.GetPositionAtOffset(word.Length);
position.InsertTextInRun(text);
rtb.CaretPosition = position.GetPositionAtOffset(text.Length);
}
}
}
private static IList<(string, TextRange)> CalculateTextRange(TextRange range, string pattern, int maxwords)
{
TextRange search = range;
int correction = 0;
var result = new List<(string, TextRange)>();
// Enumerate all found mathes and creating list of Word/TextRange pairs
var regExp = new Regex(pattern.ToString(), RegexOptions.IgnoreCase);
foreach (Match match in regExp.Matches(range.Text))
{
if (CalculateTextRange(search, match.Index - correction, match.Length) is TextRange tr)
{
result.Add((match.Value, tr));
correction = match.Index + match.Length;
search = new TextRange(tr.End, search.End);
if (--maxwords <= 0)
break;
}
}
return result;
}
// Returns a `TextRange` of the string started from `iStart` index
// and having `length` characters or `null` if no string found.
private static TextRange CalculateTextRange(TextRange search, int iStart, int length)
{
return (GetTextPositionAtOffset(search.Start, iStart) is TextPointer start)
? new TextRange(start, GetTextPositionAtOffset(start, length))
: null;
}
private static TextPointer GetTextPositionAtOffset(TextPointer position, int offset)
{
for (TextPointer current = position; current != null; current = position.GetNextContextPosition(LogicalDirection.Forward))
{
position = current;
var adjacent = position.GetAdjacentElement(LogicalDirection.Forward);
var context = position.GetPointerContext(LogicalDirection.Forward);
switch (context)
{
case TextPointerContext.Text:
int count = position.GetTextRunLength(LogicalDirection.Forward);
if (offset <= count)
{
return position.GetPositionAtOffset(offset);
}
offset -= count;
break;
case TextPointerContext.ElementStart:
if (adjacent is InlineUIContainer)
offset--;
break;
case TextPointerContext.ElementEnd:
if (adjacent is Paragraph)
offset -= 2;
break;
default:
break;
}
}
return position;
}
}