If you are only given an index and length (or EndIndex) of a certain text to select, how do you perform this in WPF version of RichTextBox?
This is very doable in Textbox as you can call Textbox.Select(startIndex,Length) but I don't see anything equivalent in RTB.
Edit: I have found the answer to making a selection
internal string Select(RichTextBox rtb, int index, int length)
{
TextRange textRange = new TextRange(rtb.Document.ContentStart, rtb.Document.ContentEnd);
if (textRange.Text.Length >= (index + length))
{
TextPointer start = textRange.Start.GetPositionAtOffset(index, LogicalDirection.Forward);
TextPointer end = textRange.Start.GetPositionAtOffset(index + length, LogicalDirection.Backward);
rtb.Selection.Select(start, end);
}
return rtb.Selection.Text;
}
However, when I try to highlight the line after the selection has been made:
rtb.Selection.ApplyPropertyValue(TextElement.BackgroundProperty, new SolidColorBrush(Colors.LightBlue));
The highlighting feature works only on the first try and breaks after second try. Anyone know the reason for this?
Ok this question is old but i finally found the Answer so i put this here.
I was having similiar issues when i tried to make some Syntaxhighlighter using the RichTextBox.
What I found out is, that when you play arround with ApplyPropertyValue you cannot simply use GetPositionAtOffset anymore. I believe that applying propertyvalues seems to change the "internal positions" of TextTokens inside the Document, hence 'braking' this functionality.
The Workaround:
Everytime you need to work with GetPositionAtOffset first call ClearAllProperties on the complete TextRange of the Document, then reapply all your properties using ApplyPropertyValue but thistime from right to left. (right means highest offset)
I dont know if you had applied any PropertyValues expect the Selection highlighting, so you might need to put more thinking inthere.
This is how my code looked when it caused the Problem:
private void _highlightTokens(FlowDocument document)
{
TextRange fullRange = new TextRange(document.ContentStart, document.ContentEnd);
foreach (Token token in _tokenizer.GetTokens(fullRange.Text))
{
TextPointer start = fullRange.Start.GetPositionAtOffset(token.Position);
TextPointer end = start.GetPositionAtOffset(token.Length);
TextRange range = new TextRange(start, end);
range.ApplyPropertyValue(TextElement.ForegroundProperty, _getTokenColor(token));
}
}
And i fixed it like this:
private void _highlightTokens(FlowDocument document)
{
TextRange fullRange = new TextRange(document.ContentStart, document.ContentEnd);
fullRange.ClearAllProperties(); // NOTICE: first remove allProperties.
foreach (Token token in _tokenizer.GetTokens(fullRange.Text).Reverse()) // NOTICE: Reverse() to make the "right to left" work
{
TextPointer start = fullRange.Start.GetPositionAtOffset(token.Position);
TextPointer end = start.GetPositionAtOffset(token.Length);
TextRange range = new TextRange(start, end);
range.ApplyPropertyValue(TextElement.ForegroundProperty, _getTokenColor(token));
}
}
Use the Select() method on the RichTextBox.Selection property.
Blockquote you can get the text between spaces.....
private string RichWordOver(RichTextBox rch, int x, int y)
{
int pos = rch.GetCharIndexFromPosition(new Point(x, y));
if (pos <= 0) return "";
string txt = rch.Text;
int start_pos;
for (start_pos = pos; start_pos >= 0; start_pos--)
{
char ch = txt[start_pos];
if (!char.IsLetterOrDigit(ch) && !(ch=='_')) break;
}
start_pos++;
int end_pos;
for (end_pos = pos; end_pos < txt.Length; end_pos++)
{
char ch = txt[end_pos];
if (!char.IsLetterOrDigit(ch) && !(ch == '_')) break;
}
end_pos--;
if (start_pos > end_pos) return "";
return txt.Substring(start_pos, end_pos - start_pos + 1);
}
private void rchText_MouseClick(object sender, MouseEventArgs e)
{
MessageBox.Show(RichWordOver(rchText, e.X, e.Y));
}
Related
I am trying to set the position of caret in richtextbox based on index position of a word. Even though I am able to change the caret position, the caret does not move to the correct location.
Here is my sample code:
private void Button_Click(object sender, RoutedEventArgs e)
{
RTB_Main.Document.Blocks.Clear();
for (int i = 0; i < 10; i++)
{
Paragraph para = new Paragraph(new Run(i + ""));
RTB_Main.Document.Blocks.Add(para);
}
TextRange richText = new TextRange(RTB_Main.Document.ContentStart, RTB_Main.Document.ContentEnd);
string searchText = tb_Search.Text; // 1 to 9
int position = Regex.Match(richText.Text, searchText).Index;
RTB_Main.CaretPosition = RTB_Main.Document.ContentStart;
RTB_Main.CaretPosition = RTB_Main.CaretPosition.GetPositionAtOffset(position);
RTB_Main.Focus();
}
What is wrong with this approach?
Also, Please let me know if there is a better way to set the caret position to an index?
The problem in my case was caused by new line characters \r\n. I just replaced these with another characters and it worked for me. Note that I am replacing them with not 2 characters but 4.
private void Button_Click(object sender, RoutedEventArgs e)
{
RTB_Main.Document.Blocks.Clear();
for (int i = 0; i < 10; i++)
{
Paragraph para = new Paragraph(new Run(i + ""));
RTB_Main.Document.Blocks.Add(para);
}
TextRange richText = new TextRange(RTB_Main.Document.ContentStart, RTB_Main.Document.ContentEnd);
string searchText = tb_Search.Text; // 1 to 9
string tmpStr = richText.Text.Replace("\r\n", "....");
int position = Regex.Match(tmpStr, searchText).Index;
RTB_Main.CaretPosition = RTB_Main.Document.ContentStart;
RTB_Main.CaretPosition = RTB_Main.CaretPosition.GetPositionAtOffset(position);
RTB_Main.Focus();
}
As Maciek noted, there are invisible formatting items that affects the count. My code adds a feedback loop because we are able to ask what the true caret position is. It feels hacky but I could not find anything better.
public static void SetCaretPositionOfRichTextBoxToCharIndex(
System.Windows.Controls.RichTextBox box, int charIndex)
{
// RichTextBox contains many formattings, and they, although invisible, count
// when setting CaretPosition. Calling GetPositionAtOffset with charIndex from
// DocumentStart can be less than the necessary CaretPosition. This code
// therefore has a feedback loop to see how much more offset is necessary.
box.CaretPosition = box.CaretPosition.DocumentStart;
int attemptedCharIndex = 0;
int fixerInc = 0;
while (attemptedCharIndex < charIndex)
{
box.CaretPosition = box.CaretPosition.GetPositionAtOffset(charIndex - attemptedCharIndex + fixerInc);
int temp = new TextRange(box.Document.ContentStart, box.CaretPosition).Text.Length;
if (attemptedCharIndex == temp)
{
fixerInc++;
}
else
{
fixerInc = 0
}
attemptedCharIndex = temp;
}
}
I am looking a way to create a HyperLink in a RichTextBox pointing to a line of the text of the same RichTextBox.
I just found how to do that with Internet Links but I don't find a way to do it with the same text inside of the control (It's like Hyperlinks in MS Word pointing to a header or bookmark).
Thanks in Advance. - CCB
No, this will not work unless you code the necessary stuff yourself.
Two suggestions:
A simple workaround with links always starting with www.
The nicer solution with arbitrary link text
Let's have a look at both options..:
Using the built-in functionality of recognizing an URL seems the right way to start, but the link will always have to look like a URL, not like a hyperlink to an anchor.. If you can live with a solution that has, say, links like this: www.goto.234 and anchors like this: #234# this is really rather simple..
A working example can be as simple as this:
private void richTextBox1_LinkClicked(object sender, LinkClickedEventArgs e)
{
var s = e.LinkText.Split('.');
string anchor = s[2];
int a = richTextBox1.Text.IndexOf("#" + anchor + "#" );
if (a >= 0) richTextBox1.SelectionStart = a; else return; // do add more checks!
richTextBox1.SelectionLength = 0;
Text = anchor + " # " + a;
//richTextBox1.ScrollToCaret(); <<--- this crashes on my machine!
// so I take the jump out of the click event and it works:
Timer ttt = new Timer() { Interval = 100 };
ttt.Tick += (ss, ee) => { richTextBox1.ScrollToCaret(); ttt.Stop(); };
}
Option two: If you'd rather have more choice of how the links should read you can do this:
Start by formatting each to
Start with a special character, say a tilde '~'
format it to look blue and underlined if you want to
Either make it one word or replace space by underlines and format those to have the forecolor equal to the backcolor
Now this can do the job:
public string delimiters = " ()[]{}!&?=/\\,;.\r\n";
private void richTextBox2_Click(object sender, EventArgs e)
{
int sstart = -1;
string s = getWordAt(richTextBox2.Text,
richTextBox2.SelectionStart, delimiters, out sstart);
if (s.Length < 3) return;
string char1 = s.Substring(0, 1);
if (char1 == "~")
{
int p = richTextBox2.Text.IndexOf("#" + s.Substring(1));
if (p >= 0) { richTextBox2.SelectionStart = p; richTextBox2.ScrollToCaret(); }
}
}
public static string getWordAt(string text, int cursorPos,
string delimiters, out int selStart)
{
int startPos = 0;
selStart = startPos;
if ((cursorPos < 0) | (cursorPos > text.Length) | (text.Length == 0)) return "";
if ((text.Length > cursorPos) & (delimiters.Contains(text[cursorPos]))) return "";
int endPos = text.Length - 1;
if (cursorPos == text.Length) endPos = text.Length - 1;
else { for (int i = cursorPos; i < text.Length; i++)
{ if (delimiters.Contains(text[i])) { endPos = i - 1; break; } } }
if (cursorPos == 0) startPos = 0;
else { for (int i = cursorPos; i > 0; i--)
{ if (delimiters.Contains(text[i])) { startPos = i + 1; break; } } }
selStart = startPos;
return text.Substring(startPos, endPos - startPos + 1);
}
Here are the two versions side by side, once at the top then after clicking on a link:
Both versions work fine, although both could do with some more checks.
Note that I was too lazy to format the pseudo-links in the second example, so they show their tildes and hashes..
Not hard to write a helper function that can insert the formatting; the search will still work as it searches in the Text, not the Rtf property..
I want to highlight some parts of text in a FlowDocument based on the search results. What I am doing is getting the indexes where the searched word occures in the text of the FlowDocument and then apply background color on the text range starting at the found index, ending at the found index + search word length.
TextRange content = new TextRange(myFlowDocument.ContentStart,
myFlowDocument.ContentEnd);
List<int> highlights = GetHighlights(content.Text, search);
foreach (int index in highlights)
{
var start = myFlowDocument.ContentStart;
var startPos = start.GetPositionAtOffset(index);
var endPos = start.GetPositionAtOffset(index + search.Length);
var textRange = new TextRange(startPos, endPos);
textRange.ApplyPropertyValue(TextElement.BackgroundProperty,
new SolidColorBrush(Colors.Yellow));
}
TextRange newRange = new TextRange(myFlowDocument.ContentStart,
newDocument.ContentEnd);
FlowDocument fd = (FlowDocument)XamlReader.Parse(newRange.Text);
The problem is, that I am searching indexes in the text of the document but when I'm returning the FlowDocument the xaml tags are added and I see the highlights moved.
How can I fix it?
Finaly, inspired by the answer of user007, after making some modifications I managed to highlight all the occurences of a string in a FlowDocument. Here is my code:
for (TextPointer position = newDocument.ContentStart;
position != null && position.CompareTo(newDocument.ContentEnd) <= 0;
position = position.GetNextContextPosition(LogicalDirection.Forward))
{
if (position.CompareTo(newDocument.ContentEnd) == 0)
{
return newDocument;
}
String textRun = position.GetTextInRun(LogicalDirection.Forward);
StringComparison stringComparison = StringComparison.CurrentCulture;
Int32 indexInRun = textRun.IndexOf(search, stringComparison);
if (indexInRun >= 0)
{
position = position.GetPositionAtOffset(indexInRun);
if (position != null)
{
TextPointer nextPointer = position.GetPositionAtOffset(search.Length);
TextRange textRange = new TextRange(position, nextPointer);
textRange.ApplyPropertyValue(TextElement.BackgroundProperty,
new SolidColorBrush(Colors.Yellow));
}
}
}
You need to iterate using GetNextContextPosition(LogicalDirection.Forward) and get TextPointer, use this one with previous TextPointer to construct TextRange. On this TextRange you can apply your logic.
What you can't do is use single TextRange from FlowDocument for searching text. FlowDocument is not only text:
private void Button_Click(object sender, RoutedEventArgs e)
{
String search = this.content.Text;
TextPointer text = doc.ContentStart;
while (true)
{
TextPointer next = text.GetNextContextPosition(LogicalDirection.Forward);
if (next == null)
{
break;
}
TextRange txt = new TextRange(text, next);
int indx = txt.Text.IndexOf(search);
if (indx > 0)
{
TextPointer sta = text.GetPositionAtOffset(indx);
TextPointer end = text.GetPositionAtOffset(indx + search.Length);
TextRange textR = new TextRange(sta, end);
textR.ApplyPropertyValue(TextElement.BackgroundProperty, new SolidColorBrush(Colors.Yellow));
}
text = next;
}
}
UPDATE:
it does not work allways, for example if you have list, special characters (\t) are counted by IndexOf, but GetPositionAtOffset doesn't count them in:
• ListItem 1
• ListItem 2
• ListItem 3
this line:
int indx = txt.Text.IndexOf(search);
could be replaced with:
int indx = Regex.Replace(txt.Text,
"[^a-zA-Z0-9_.]+", "", RegexOptions.Compiled).IndexOf(search);
kzub's answer did not seem to work for me so I also created a solution expanding on user007 to highlight all instances of a substring in TextPointer text. It also ignores case and will highlight all matches:
public static void HighlightWords(TextPointer text, string searchWord, string stringText)
{
int instancesOfSearchKey = Regex.Matches(stringText.ToLower(), searchWord.ToLower()).Count;
for (int i = 0; i < instancesOfSearchKey; i++)
{
int lastInstanceIndex = HighlightNextInstance(text, searchWord);
if (lastInstanceIndex == -1)
{
break;
}
text = text.GetPositionAtOffset(lastInstanceIndex);
}
}
private static int HighlightNextInstance(TextPointer text, string searchWord)
{
int indexOfLastInstance = -1;
while (true)
{
TextPointer next = text.GetNextContextPosition(LogicalDirection.Forward);
if (next == null)
{
break;
}
TextRange newText = new TextRange(text, next);
int index = newText.Text.ToLower().IndexOf(searchWord.ToLower());
if (index != -1)
{
indexOfLastInstance = index;
}
if (index > 0)
{
TextPointer start = text.GetPositionAtOffset(index);
TextPointer end = text.GetPositionAtOffset(index + searchWord.Length);
TextRange textRange = new TextRange(start, end);
textRange.ApplyPropertyValue(TextElement.BackgroundProperty, new SolidColorBrush(Colors.Yellow));
}
text = next;
}
return indexOfLastInstance;
}
I'm in trouble, today I tried to color some diffents words in differents lines clicked on a button. Can You explain me how to do this? I was able to do only this:
private void button1_Click(object sender, EventArgs e)
{
richTextBox1.Select(int start , int length); //It's wrong but It explains How to use .Select if you know start and length...
richTextBox1.SelectionColor = Color.Blue;
}
But how can I work on a line I know the number of, having already the text into the RichTextBox?
Thanks.
If lines are separated by \n, your problem is then to count the number of \n characters before getting to the wanted line.
For that, you can use an extension method:
public static int NthIndexOf(this String str, String match, int occurence) {
int i = 1;
int index = 0;
while (i <= occurence && ( index = str.IndexOf(match, index + 1) ) != -1) {
if (i == occurence) {
// Occurence match found!
return index;
}
i++;
}
// Match not found
return -1;
}
Now, you can find start and end values to color the selection:
private void button1_Click(object sender, EventArgs e) {
int lineNb = 13; // I assume you get this value initialized somewhere,
// I wrote 13 for the example
int start = richTextBox1.Text.NthIndexOf("\n", lineNb);
int length = richTextBox1.Text.NthIndexOf("\n", lineNb + 1);
richTextBox1.Select(start , length);
richTextBox1.SelectionColor = Color.Blue;
}
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);
}
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...)
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);
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;
}
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;
}
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));
private void SelectText(int start, int length)
{
TextRange textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
TextPointer pointerStart = textRange.Start.GetPositionAtOffset(start, LogicalDirection.Forward);
TextPointer pointerEnd = textRange.Start.GetPositionAtOffset(start + length, LogicalDirection.Backward);
richTextBox.Selection.Select(pointerStart, pointerEnd);
}
Incidentally (and this may be academic to all but myself), if you set FocusManager.IsFocusScope="True" on the container of the RichTextBox, a Grid for example,
<Grid FocusManager.IsFocusScope="True">...</Grid>
then you should be able to use Johan Danforth's Colorize method without the two invocations of ApplyPropertyValue, and the RichTextBox ought to use the default selection Background and Foreground to highlight the selection.
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);
}
Have not tried it with the RichTextBox, but it works quite well when templating a find TextBox in a FlowDocumentReader. Just to be sure you might also set
<RichTextBox FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}">...</RichTextBox>
to ensure the RichTextBox has focus within its focus scope.
The downside of this, of course, is that if the user clicks or performs a selection within the RichTextBox, your selection disappears.
private TextPointer GetPoint(TextPointer start, int pos)
{
var ret = start;
int i = 0;
while (i < pos)
{
if (ret.GetPointerContext(LogicalDirection.Forward) ==
TextPointerContext.Text)
i++;
if (ret.GetPositionAtOffset(1, LogicalDirection.Forward) == null)
return ret;
ret = ret.GetPositionAtOffset(1, LogicalDirection.Forward);
}
return ret;
}