I have a WPF RichTextBox that I am typing some text into and then parsing the whole of the text to do processing on. During this parse, I have the absolute character positions of the start and end of each word.
I would like to use these character positions to apply formatting to certain words. However, I have discovered that the FlowDocument uses TextPointer instances to mark positions in the document.
I have found that I can create a TextRange by constructing it with start and end pointers. Once I have the TextRange I can easily apply formatting to the text within it. I have been using GetPositionAtOffset to get a TextPointer for my character offset but suspect that its offset is different from mine because the selected text is in a slightly different position from what I expect.
My question is, how can I accurately convert an absolute character position to a TextPointer?
I didn't find a reliable way of converting absolute character positions into TextPosition instances.
My alternative solution was to modify the original parse to work on individual runs rather than capturing the whole text of the RichTextBox. Working with character positions that are relative to a specific Run instance has proved reliable for me. I think that moving my mindset more towards the WPF way of thinking has helped.
I took the following approach for navigating runs in the FlowDocument (inspired by http://blogs.msdn.com/prajakta/archive/2006/10/12/customize-richtextbox-to-allow-only-plain-text-input.aspx):
// Get starting pointer
TextPointer navigator = flowDocument.ContentStart;
// While we are not at end of document
while (navigator.CompareTo(flowDocument.ContentEnd) < 0)
{
// Get text pointer context
TextPointerContext context = navigator.GetPointerContext(LogicalDirection.Backward);
// Get parent as run
Run run = navigator.Parent as Run;
// If start of text element within run
if (context == TextPointerContext.ElementStart && run != null)
{
// Get text of run
string runText = run.Text;
// ToDo: Parse run text
}
// Get next text pointer
navigator = navigator.GetNextContextPosition(LogicalDirection.Forward);
}
I have also had this problem and has ended up with the following RichTextBox extension method. In my context it work flawlessly!
/// <summary>
/// Gets the text pointer at the given character offset.
/// Each line break will count as 2 chars.
/// </summary>
/// <param name="richTextBox">The rich text box.</param>
/// <param name="offset">The offset.</param>
/// <returns>The TextPointer at the given character offset</returns>
public static TextPointer GetTextPointerAtOffset(this RichTextBox richTextBox, int offset)
{
var navigator = richTextBox.Document.ContentStart;
int cnt = 0;
while (navigator.CompareTo(richTextBox.Document.ContentEnd) < 0)
{
switch (navigator.GetPointerContext(LogicalDirection.Forward))
{
case TextPointerContext.ElementStart:
break;
case TextPointerContext.ElementEnd:
if (navigator.GetAdjacentElement(LogicalDirection.Forward) is Paragraph)
cnt += 2;
break;
case TextPointerContext.EmbeddedElement:
// TODO: Find out what to do here?
cnt++;
break;
case TextPointerContext.Text:
int runLength = navigator.GetTextRunLength(LogicalDirection.Forward);
if (runLength > 0 && runLength + cnt < offset)
{
cnt += runLength;
navigator = navigator.GetPositionAtOffset(runLength);
if (cnt > offset)
break;
continue;
}
cnt++;
break;
}
if (cnt > offset)
break;
navigator = navigator.GetPositionAtOffset(1, LogicalDirection.Forward);
} // End while.
return navigator;
}
I had exact similar problem, I found out that there is a bug in RichTextBox because it does not count "new line characters - \r\n", so as your line numbers increase, you will find that your offset is positioned wrong by line number count, and I had solved my problems by offsetting line number from offset.