Select Range of Text in WPF RichTextBox (FlowDocument) Programmatically
Solution 1
Public Function GoToPoint(ByVal start As TextPointer, ByVal x As Integer) As TextPointer
Dim out As TextPointer = start
Dim i As Integer = 0
Do While i < x
If out.GetPointerContext(LogicalDirection.Backward) = TextPointerContext.Text Or _
out.GetPointerContext(LogicalDirection.Backward) = TextPointerContext.None Then
i += 1
End If
If out.GetPositionAtOffset(1, LogicalDirection.Forward) Is Nothing Then
Return out
Else
out = out.GetPositionAtOffset(1, LogicalDirection.Forward)
End If
Loop
Return out
End Function
Try this, this should return a text pointer for the given char offset. (Sorry its in VB, but thats what I am working in...)
Solution 2
Try that :
var textRange = MyRichTextBox.Selection;
var start = MyRichTextBox.Document.ContentStart;
var startPos = start.GetPositionAtOffset(3);
var endPos = start.GetPositionAtOffset(8);
textRange.Select(startPos, endPos);
textRange.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.Blue));
textRange.ApplyPropertyValue(TextElement.FontWeightProperty, FontWeights.Bold);
Solution 3
I tried using the solution posted by KratzVB but found that it was ignoring newlines. If you want to count \r and \n symbols then this code should work:
private static TextPointer GetPoint(TextPointer start, int x)
{
var ret = start;
var i = 0;
while (ret != null)
{
string stringSoFar = new TextRange(ret, ret.GetPositionAtOffset(i, LogicalDirection.Forward)).Text;
if (stringSoFar.Length == x)
break;
i++;
if (ret.GetPositionAtOffset(i, LogicalDirection.Forward) == null)
return ret.GetPositionAtOffset(i-1, LogicalDirection.Forward)
}
ret=ret.GetPositionAtOffset(i, LogicalDirection.Forward);
return ret;
}
Solution 4
Could not find a solution with acceptable performance solution to this problem for a long time. Next sample works in my case with the highest performance. Hope it will help somebody as well.
TextPointer startPos = rtb.Document.ContentStart.GetPositionAtOffset(searchWordIndex, LogicalDirection.Forward);
startPos = startPos.CorrectPosition(searchWord, FindDialog.IsCaseSensitive);
if (startPos != null)
{
TextPointer endPos = startPos.GetPositionAtOffset(textLength, LogicalDirection.Forward);
if (endPos != null)
{
rtb.Selection.Select(startPos, endPos);
}
}
public static TextPointer CorrectPosition(this TextPointer position, string word, bool caseSensitive)
{
TextPointer start = null;
while (position != null)
{
if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
{
string textRun = position.GetTextInRun(LogicalDirection.Forward);
int indexInRun = textRun.IndexOf(word, caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase);
if (indexInRun >= 0)
{
start = position.GetPositionAtOffset(indexInRun);
break;
}
}
position = position.GetNextContextPosition(LogicalDirection.Forward);
}
return start;
}
Solution 5
My version based on cave_dweller's version
private static TextPointer GetPositionAtCharOffset(TextPointer start, int numbertOfChars)
{
var offset = start;
int i = 0;
string stringSoFar="";
while (stringSoFar.Length < numbertOfChars)
{
i++;
TextPointer offsetCandidate = start.GetPositionAtOffset(
i, LogicalDirection.Forward);
if (offsetCandidate == null)
return offset; // ups.. we are to far
offset = offsetCandidate;
stringSoFar = new TextRange(start, offset).Text;
}
return offset;
}
To omit some characters add this code inside loop:
stringSoFar = stringSoFar.Replace("\r\n", "")
.Replace(" ", "")
Instead of this (slow):
var startPos = GetPoint(start, offset);
var endPos = GetPoint(start, offset + length);
You should do this (faster)
var startPos = GetPoint(start, offset);
var endPos = GetPoint(startPos, length);
Or create separate method to get TextRange:
private static TextRange GetTextRange(TextPointer start, int startIndex, int length)
{
var rangeStart = GetPositionAtCharOffset(start, startIndex);
var rangeEnd = GetPositionAtCharOffset(rangeStart, length);
return new TextRange(rangeStart, rangeEnd);
}
You can now format text without Select()
ing:
var range = GetTextRange(Document.ContentStart, 3, 8);
range.ApplyPropertyValue(
TextElement.BackgroundProperty,
new SolidColorBrush(Colors.Aquamarine));
Comments
-
Johan Danforth almost 2 years
I have this WPF RichTextBox and I want to programmatically select a given range of letters/words and highlight it. I've tried this, but it doesn't work, probably because I'm not taking into account some hidden FlowDocument tags or similar. For example, I want to select letters 3-8 but 2-6 gets selected):
var start = MyRichTextBox.Document.ContentStart; var startPos = start.GetPositionAtOffset(3); var endPos = start.GetPositionAtOffset(8); var textRange = new TextRange(startPos,endPos); textRange.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.Blue)); textRange.ApplyPropertyValue(TextElement.FontWeightProperty, FontWeights.Bold);
I've realised RichTextBox handling is a bit trickier than I thought :)
Update: I got a few answers on the MSDN forums: This thread where "dekurver" seid:
The offsets you're specifying are not character offsets but symbol offsets. What you need to do is get a TextPointer that you know is adjacent to text, then you can add character offsets.
And "LesterLobo" said:
you will need to loop through the paragraphs and inlines to find the Next and then their offsets in a loop to apply for all appearances of the specific text. note that when you edit your text would move but your highlight wouldnt move as its associated with the offset not the text. You could however create a custom run and provide a highlight for it...
Would still LOVE to see some sample code for this if someone knows their way around FlowDocuments...
EDIT I got a version of Kratz VB code working, it looks like this:
private static TextPointer GetPoint(TextPointer start, int x) { var ret = start; var i = 0; while (i < x && ret != null) { if (ret.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.Text || ret.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.None) i++; if (ret.GetPositionAtOffset(1, LogicalDirection.Forward) == null) return ret; ret = ret.GetPositionAtOffset(1, LogicalDirection.Forward); } return ret; }
And I use it like this:
Colorize(item.Offset, item.Text.Length, Colors.Blue); private void Colorize(int offset, int length, Color color) { var textRange = MyRichTextBox.Selection; var start = MyRichTextBox.Document.ContentStart; var startPos = GetPoint(start, offset); var endPos = GetPoint(start, offset + length); textRange.Select(startPos, endPos); textRange.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(color)); textRange.ApplyPropertyValue(TextElement.FontWeightProperty, FontWeights.Bold); }
-
Johan Danforth over 14 years@Tomas not for me I'm afraid. letters 2-6 gets selected/colored for me using that code. I'm going to try something else and get back here.
-
Johan Danforth over 14 yearsNice! I got a version of that code working, will add it to the question. Cheers.
-
devios1 almost 14 yearsThis is also handy for counting the characters in a RichTextBox: just do the loop while
out
is not null and returni
at the end. -
a7madx7 almost 11 yearsThis method gets me every single character that comes after any of my specified tokens i need it to just get a pointer to my word only because when i try to make it bold for example it makes the whole sentence after my token " bold" i just need it to operate on my token!
-
a7madx7 almost 11 yearslike the visual studio does with its preserved keywords !
-
Nick almost 11 yearsThis doesn't work for single characters or for two adjacent elements.
-
CoperNick over 10 yearsMaking i+=1 for backward context == None (no character) is wrong. Backward in this case means you are looking to character before start pointer - this is also wrong - you should look forward. This also do not work for line breaks. See @cave_dweller and my solution.
-
BrainSlugs83 over 10 yearsRight, this will give you a text pointer -- but how do you SELECT it?
-
BrainSlugs83 over 10 yearsThanks for that -- I knew I would either have to set
.Selection
or call.Select(...)
-- but having to call.Selection.Select
? Completely unexpected. -
Sameera Kumarasingha over 8 yearsYes. this works, i had a problem due MyRichTextBox was out of focus. So if there are any problems, first focus it programmatically, ( MyRichTextBox.focus()).
-
Ed Elliott over 8 yearsthis works for me - you need to add a semi-colon after "return ret.GetPositionAtOffset(i-1, LogicalDirection.Forward)" - i tried to edit but the edit is shorter than 6 characters (facepalm)
-
Richard Erickson about 8 yearsWelcome to SO and thank you for posting an answer. Please consider expanding your answer to include an explanation of your code.
-
DanW over 7 yearsAfter the i+=1, I had to add (in C#): "if (i==x) return out;" (of course, I renamed the out variable because "out" is a reserved word)
-
gehbiszumeis about 5 yearsPlease put your answer always in context instead of just pasting code. See here for more details.