just like this image from Avalon:
how to do the right positioning for a context-hint or intellisense?
senario:
im working on a code-editor and i use richtextbox(rtb) as the c-editor and the combobox(lb) as the context-hint .
all codes are almost done() except for right positioning for combobox(context hint) .
i use this code:
Point cursorPt = Cursor.Position;
lb.Location = PointToClient(cursorPt);
but its appearing when mouse cursor was ... if mouse cursor was outside the form it will not appear also .
further more heres the codes:
public void TextChangedEvent(object sender, EventArgs e)
{
lb.BringToFront();
RichTextBox rtb = sender as RichTextBox;
if (rtb != null)
{
//pass through to the HighlightType class
HighlighType HighlighType = new HighlighType(rtb);
lb.Visible = false;
lb.Items.Clear();
}
// Calculate the starting position of the current line.
int start = 0, end = 0;
for (start = rtb.SelectionStart - 1; start > 0; start--)
{
if (rtb.Text[start] == '\n') { start++; break; }
}
// Calculate the end position of the current line.
for (end = rtb.SelectionStart; end < rtb.Text.Length; end++)
{
if (rtb.Text[end] == '\n') break;
}
// Extract the current line that is being edited.
String line = rtb.Text.Substring(start, end - start);
// Backup the users current selection point.
int selectionStart = rtb.SelectionStart;
int selectionLength = rtb.SelectionLength;
// Split the line into tokens.
Regex r = new Regex("([ \\t{}();])");
Regex singlequote = new Regex("\'[^\"]*\'");
Regex doublequote = new Regex("\"[^\"]*\"");
string[] tokens = r.Split(line);
int index = start;
foreach (string token in tokens)
{
// Set the token's default color and font.
rtb.SelectionStart = index;
rtb.SelectionLength = token.Length;
rtb.SelectionColor = Color.Black;
rtb.SelectionFont = new Font("Courier New", 10, FontStyle.Regular);
if (token == "//" || token.StartsWith("//"))
{
// Find the start of the comment and then extract the whole comment.
int length = line.Length - (index - start);
string commentText = rtb.Text.Substring(index, length);
rtb.SelectionStart = index;
rtb.SelectionLength = length;
HighlighType.commentsType(rtb);
break;
}
//*** invention code:
Point cursorPt = Cursor.Position;
lb.Location = PointToClient(cursorPt);
KeyWord keywordsL = new KeyWord();
KeyWord eventsL = new KeyWord();
if (token == "." || token.StartsWith(".") & token.EndsWith(" "))
{
foreach (string str in keywordsL.keywords)
{
lb.Items.Add(str);
lb.Visible = true;
lb.Focus();
}
ty in advance!
Point point = this.rtb.GetPositionFromCharIndex(rtb.SelectionStart);
this.lb.Location = point;
Related
i am working with WP richtextbox.i done to navigate each line from current caret position to nextline,previousline etc.it works fine.i need to dynamically change fontsize in richtextbox.
i used this below methods to change font size:
myrichtextbox.SetValue(TextElement.FontSizeProperty, fontSizedouble +10);
myrichtextbox.FontSize = (txtAppendValue.FontSize + 10);
it works.But after execute this methods,the other functionality execution time taken is high.Before that NavigateNextLine() taken 15ms to 20ms.After execution it takes 40 to 50 ms.i continuously call the fontSize 4,5 times then the NavigateNextLine() takes 100ms t0 120 ms.
public void NavigateNextLine()
{
Int32 lineNumber;
txtAppendValue.CaretPosition.GetLineStartPosition(-int.MaxValue, out lineNumber);
Int32 iLineIndex = System.Math.Abs(lineNumber);
Int32 iCurrentStart = 0;
Int32 iCurWordLength = 0;
for (Int32 icnt = 0; icnt <= iLineIndex; icnt++)
{
m_strCurLineText = GetLineText(txtAppendValue.CaretPosition.GetLineStartPosition(lineNumber), 0, null);
iCurrentStart = iCurrentStart + m_strCurLineText.Length;
lineNumber += 1;
}
String[] strArr = m_strCurLineText.Split(' ');
if (strArr.Length > 0)
{
iCurWordLength = strArr[0].Length; // Get the first word length of current line
if (iCurWordLength == 0)
{
iCurWordLength = strArr[1].Length;
iCurrentStart = iCurrentStart + 1;
}
}
else
{
iCurWordLength = m_strCurLineText.Length; //to get single word line length
}
NewStart = iCurrentStart;
}
String GetLineText(TextPointer TextPointer, int LineRltv = 0, string Default = null)
{
TextPointer tp1 = TextPointer.GetLineStartPosition(LineRltv);
if (tp1 == null)
{
return Default;
}
else
{
tpNextLine2 = tp1.GetLineStartPosition(1);
TextRange tr = null;
if (tpNextLine2 == null)
{
tpNextLine2 = txtAppendValue.Document.ContentEnd;
}
tr = new TextRange(tp1, tpNextLine2);
return tr.Text;
}
}
SO whats the problem?how to resolve it?
regards
Arjun
Both should work:
txtAppendValue.ApplyPropertyValue(TextElement.FontSizeProperty, (double)10);
OR
txtAppendValue.ApplyPropertyValue(TextElement.FontSizeProperty, 10.0)
I'm trying to find instances of a string in a WPF RichTextBox. What I have now almost works, but it highlights the wrong section of the document.
private int curSearchLocation;
private void FindNext_Click(object sender, RoutedEventArgs e)
{
TextRange text = new TextRange(RichEditor.Document.ContentStart, RichEditor.Document.ContentEnd);
var location = text.Text.IndexOf(SearchBox.Text, curSearchLocation, StringComparison.CurrentCultureIgnoreCase);
if (location < 0)
{
location = text.Text.IndexOf(SearchBox.Text, StringComparison.CurrentCultureIgnoreCase);
}
if (location >= 0)
{
curSearchLocation = location + 1;
RichEditor.Selection.Select(text.Start.GetPositionAtOffset(location), text.Start.GetPositionAtOffset(location + SearchBox.Text.Length));
}
else
{
curSearchLocation = 0;
MessageBox.Show("Not found");
}
RichEditor.Focus();
}
This is what happens when I search for "document":
This is because GetPositionAtOffset includes non-text elements such as opening and closing tags in its offset, which is not what I want. I couldn't find a way to ignore these elements, and I also couldn't find a way to directly get a TextPointer to the text I want, which would also solve the problem.
How can I get it to highlight the correct text?
Unfortunately the TextRange.Text strips out non-text characters, so in this case the offset computed by IndexOf will be slightly too low. That is the main problem.
I tried to solve your problem and found working solution that works fine even when we have formatted text in many paragraphs.
A lot of help is taken from this CodeProject Article. So also read that article.
int curSearchLocation;
private void FindNext_Click(object sender, RoutedEventArgs e)
{
TextRange text = new TextRange(RichEditor.Document.ContentStart, RichEditor.Document.ContentEnd);
var location = text.Text.IndexOf(SearchBox.Text, curSearchLocation, StringComparison.CurrentCultureIgnoreCase);
if (location < 0)
{
location = text.Text.IndexOf(SearchBox.Text, StringComparison.CurrentCultureIgnoreCase);
}
if (location >= 0)
{
curSearchLocation = location + 1;
Select(location, SearchBox.Text.Length);
}
else
{
curSearchLocation = 0;
MessageBox.Show("Not found");
}
RichEditor.Focus();
}
public void Select(int start, int length)
{
TextPointer tp = RichEditor.Document.ContentStart;
TextPointer tpLeft = GetPositionAtOffset(tp, start, LogicalDirection.Forward);
TextPointer tpRight = GetPositionAtOffset(tp, start + length, LogicalDirection.Forward);
RichEditor.Selection.Select(tpLeft, tpRight);
}
private TextPointer GetPositionAtOffset(TextPointer startingPoint, int offset, LogicalDirection direction)
{
TextPointer binarySearchPoint1 = null;
TextPointer binarySearchPoint2 = null;
// setup arguments appropriately
if (direction == LogicalDirection.Forward)
{
binarySearchPoint2 = this.RichEditor.Document.ContentEnd;
if (offset < 0)
{
offset = Math.Abs(offset);
}
}
if (direction == LogicalDirection.Backward)
{
binarySearchPoint2 = this.RichEditor.Document.ContentStart;
if (offset > 0)
{
offset = -offset;
}
}
// setup for binary search
bool isFound = false;
TextPointer resultTextPointer = null;
int offset2 = Math.Abs(GetOffsetInTextLength(startingPoint, binarySearchPoint2));
int halfOffset = direction == LogicalDirection.Backward ? -(offset2 / 2) : offset2 / 2;
binarySearchPoint1 = startingPoint.GetPositionAtOffset(halfOffset, direction);
int offset1 = Math.Abs(GetOffsetInTextLength(startingPoint, binarySearchPoint1));
// binary search loop
while (isFound == false)
{
if (Math.Abs(offset1) == Math.Abs(offset))
{
isFound = true;
resultTextPointer = binarySearchPoint1;
}
else
if (Math.Abs(offset2) == Math.Abs(offset))
{
isFound = true;
resultTextPointer = binarySearchPoint2;
}
else
{
if (Math.Abs(offset) < Math.Abs(offset1))
{
// this is simple case when we search in the 1st half
binarySearchPoint2 = binarySearchPoint1;
offset2 = offset1;
halfOffset = direction == LogicalDirection.Backward ? -(offset2 / 2) : offset2 / 2;
binarySearchPoint1 = startingPoint.GetPositionAtOffset(halfOffset, direction);
offset1 = Math.Abs(GetOffsetInTextLength(startingPoint, binarySearchPoint1));
}
else
{
// this is more complex case when we search in the 2nd half
int rtfOffset1 = startingPoint.GetOffsetToPosition(binarySearchPoint1);
int rtfOffset2 = startingPoint.GetOffsetToPosition(binarySearchPoint2);
int rtfOffsetMiddle = (Math.Abs(rtfOffset1) + Math.Abs(rtfOffset2)) / 2;
if (direction == LogicalDirection.Backward)
{
rtfOffsetMiddle = -rtfOffsetMiddle;
}
TextPointer binarySearchPointMiddle = startingPoint.GetPositionAtOffset(rtfOffsetMiddle, direction);
int offsetMiddle = GetOffsetInTextLength(startingPoint, binarySearchPointMiddle);
// two cases possible
if (Math.Abs(offset) < Math.Abs(offsetMiddle))
{
// 3rd quarter of search domain
binarySearchPoint2 = binarySearchPointMiddle;
offset2 = offsetMiddle;
}
else
{
// 4th quarter of the search domain
binarySearchPoint1 = binarySearchPointMiddle;
offset1 = offsetMiddle;
}
}
}
}
return resultTextPointer;
}
int GetOffsetInTextLength(TextPointer pointer1, TextPointer pointer2)
{
if (pointer1 == null || pointer2 == null)
return 0;
TextRange tr = new TextRange(pointer1, pointer2);
return tr.Text.Length;
}
Hope so this code will work for your case.
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 have following string:
Result = "<em>Administration</em> <em>Resources</em> Officer <em>paragraphs</em>";
Can any one guide me how to generate following string with the replacement of above string:
Result = "<em>Administration Resources</em> Officer <em>paragraphs</em> "
Basically I have multiple "Emphasized text" tag in string I want to remove which are place one word to another word and generate one "Emphasized text" tag of that two words.
while (result.Contains(" <em>")) {
result = result.Replace(" <em>", "<em>");
}
result = result.Replace("</em><em>", "");
Here is a state machine approach using switch statement that will do what you want:
public class TagCleaner
{
public string CleanEM(string input)
{
var beginTag = "<em>";
var endTag = "</em>";
var parsable = input;
var state = 0;
var output = string.Empty;
var done = false;
while (!done)
{
switch (state)
{
case 0: // new attempt... find <em>
{
var idx = parsable.IndexOf(beginTag);
if (idx < 0)
{
output = parsable;
state = -1;
}
if (idx > 0)
{
output = parsable.Substring(0, idx + beginTag.Length);
}
if (idx == 0)
{
output = beginTag;
}
parsable = parsable.Substring(idx + beginTag.Length); //chopped off anything before the <em> tag for next round
state = 1; // set state to go find matching </em>
}
break;
case 1: // found <em>, now find matching </em>
{
var idx = parsable.IndexOf(endTag);
if (idx < 0)
{
output += parsable;
state = -1;
}
if (idx > 0)
{
output += parsable.Substring(0, idx + endTag.Length);
}
if (idx == 0) //<em></em>... remove the last <em> tag from output...
{
output = output.Substring(0, output.LastIndexOf(beginTag));
}
parsable = parsable.Substring(idx + endTag.Length); //chopped off anything before the </em> tag for next round
if (parsable.Length < 1)
state = -1; //done
else
state = 2; // set state to find the next <em>
}
break;
case 2: //just found </em>, now look for the next <em>
{
var idx = parsable.IndexOf(beginTag);
if (idx < 0)
{
output += parsable;
state = -1; //done
}
if (idx >= 0)
{
var prefix = parsable.Substring(0, idx);
var re = new System.Text.RegularExpressions.Regex("^ *$");
if (re.IsMatch(prefix)) // found 0 or more spaces between the </em> and <em> tag...
{
output = output.Substring(0, output.LastIndexOf(endTag)); //chop off the last </em> from output
output += prefix; //add the spaces to the output
parsable = parsable.Substring(idx + beginTag.Length);
state = 1; //set state to go find </em>
}
else // there are other things beside empty spaces in between...
{
output += parsable.Substring(0, idx + beginTag.Length);
parsable = parsable.Substring(idx + beginTag.Length);
state = 1; //set state to go find </em>
}
}
if (parsable.Length < 1)
state = -1; //done
}
break;
default:
done = true;
break;
}
}
return output;
}
}
It'll do what you want and with a bit of changes, you can switch it to clean whatever tag you want.
Here is a sample MS Test to prove that it work...
[TestClass]
public class TagCleanerTest
{
[TestMethod]
public void Should_Clean_EM_Tag_That_Are_In_Sequence_Even_With_Spaces_In_Between()
{
var input = "<em>Administration</em> <em>Resource</em> Officer <em>paragraphs</em>";
var expected = "<em>Administration Resource</em> Officer <em>paragraphs</em>";
var sut = new TagCleaner();
var actual = sut.CleanEM(input);
Assert.AreEqual(expected, actual);
}
}
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;
}