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.
Related
I'm using the C# / WPF application.
I want to change the color of all occurrences of a specific word in a text displayed inside a RichTextBox.
I have tried several approaches but they all failed!
The method below is scanning content of the RichTextBox for specific text and color all found fragments to red color.
public static void TextSearchAndColor(RichTextBox rtb)
{
var find = "Text_To_Find";
TextRange searchRange = new TextRange(rtb.Document.ContentStart, rtb.Document.ContentEnd);
while(searchRange.FindTextInRange(find) is TextRange found)
{
found.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.Red));
searchRange = new TextRange(found.End, rtb.Document.ContentEnd);
}
}
public static class TextRangeExt
{
public static TextRange FindTextInRange(this TextRange searchRange, string searchText)
{
TextRange result = null;
int offset = searchRange.Text.IndexOf(searchText, StringComparison.OrdinalIgnoreCase);
if (offset >= 0)
{
var start = GetTextPositionAtOffset(searchRange.Start, offset);
result = new TextRange(start, GetTextPositionAtOffset(start, searchText.Length));
}
return result;
}
public static TextPointer GetTextPositionAtOffset(this 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;
}
}
return position;
}
}
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 want to select the text that is between the last '{' and '}' of a richtextbox text.
I have the next code, but I have an error on the "LastIndexOf" function and I don't know how to fix it. Can someone give me some help?
private void highlightText()
{
mRtbxOperations.SelectionStart = mRtbxOperations.Text.LastIndexOf(#"{", 1, mRtbxOperations.SelectionStart);
mRtbxOperations.SelectionLength = mRtbxOperations.Text.IndexOf(#"}", mRtbxOperations.SelectionStart, mRtbxOperations.Text.Length - 1);
mRtbxOperations.SelectionBackColor = Color.LightBlue;
mRtbxOperations.SelectionFont = new Font(mRtbxOperations.SelectionFont, FontStyle.Underline);
mRtbxOperations.SelectionLength = 0;
}
LastIndexOf Error:
The count must be positive and must refer to a location within the
string, array or collection. Parameter name: count
You LastIndexOf parameters are messed up, as well as the Length of the selection, where you need to substract the starting point in order to get the proper length.
Try a simpler version:
int textStart = mRtbxOperations.Text.LastIndexOf(#"{",
mRtbxOperations.SelectionStart);
if (textStart > -1) {
int textEnd = mRtbxOperations.Text.IndexOf(#"}", textStart);
if (textEnd > -1) {
mRtbxOperations.Select(textStart, textEnd - textStart + 1);
mRtbxOperations.SelectionBackColor = Color.LightBlue;
}
}
Seems that you're getting out of the text bounds. When you are getting a substring or an index, you always should use the string bounds, or a substring bounds. Also, you need to check that the selection is valid.
I would rewrite your code as follows:
private void highlightText()
{
Selection selection = GetSelection(mRtbxOperations.Text);
if (selection == null)
return;
mRtbxOperations.SelectionStart = selection.Start;
mRtbxOperations.SelectionLength = selection.Length;
mRtbxOperations.SelectionBackColor = Color.LightBlue;
mRtbxOperations.SelectionFont = new Font(mRtbxOperations.SelectionFont,
FontStyle.Underline);
}
private static Selection GetSelection(string text)
{
int sIndex = text.LastIndexOf(#"{");
if (sIndex == -1)
return null;
int eIndex = text.IndexOf(#"}", sIndex);
if (eIndex == -1)
return null;
return new Selection(sIndex + 1, eIndex);
}
public class Selection
{
public int Start { get; set; }
public int End { get; set; }
public int Length
{
get
{
return End - Start;
}
}
public Selection(int startIndex, int endIndex)
{
this.Start = startIndex;
this.End = endIndex;
}
}
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;
I am try find a data in richTextBox . I can try with the option of richTextBox1.Find("textBox1.text").I am new in programming . when the run this code one time find the data and can't a select whole page. I have try again and again but can't do this.
Once again i am telling what is my problem, my problem is find a data in the whole page. but find a once time data and stop. My coding part is below...plz help me...
thanks in advance..
private void gOToToolStripMenuItem_Click(object sender, EventArgs e)
{ if (richTextBox1.Text.Trim().Length > 0)
{
FindMyText("joginder", 0, richTextBox1.Text.Length);
}
}
public int FindMyText(string searchText, int searchStart, int searchEnd)
{
int returnValue = -1;
if (searchText.Length > 0 && searchStart >= 0)
{
if (searchEnd > searchStart || searchEnd == -1)
{
int indexToText = richTextBox1.Find(searchText, searchStart, searchEnd, RichTextBoxFinds.MatchCase);
if (indexToText >= 0)
{
returnValue = indexToText;
} }
}
return returnValue;
} }
You need to repeat your search through the text, advancing the start position.
Update your code as follows:
public void FindAllMatches(string searchText)
{
int start = 0;
int increment = searchText.Length;
bool complete = false;
while (!complete)
{
start = richTextBox1.Find(searchText, start, RichTextBoxFinds.MatchCase);
if (start >= 0) start += increment;
else complete = true;
}
}