Searching and Highlighting words in Richtextbox is very slow - c#

I have a RichTextBox rtbADB, I have to find the words which in richtextbox, which are either "SDM" or "SurfaceFlinger, and highlight( change fore color or change background color) them with any color. I am using the below code to do the same. There are two problems with my code:
While highlighting, it scrolls through the content of richtextbox , I want the highlight to happen without scrolling through the content.
The highlighting is very slow. I want it to happen as soon as function is called.
public void HighLightKeyWords()
{
string keywords = #"SDM|SurfaceFlinger";
MatchCollection keywordMatches = Regex.Matches(rtbADB.Text, keywords);
foreach (Match m in keywordMatches)
{
//rtbADB.Select(m.Index, m.Length);
rtbADB.SelectionStart = m.Index;
rtbADB.SelectionLength = m.Length;
rtbADB.SelectionColor = Color.Red;
}
}

Related

Automatic highlighting a part of the text in a TextBox or RichTextBox

Is it possible to highlight a part of a text without selecting this part of the text preferably with a different color in Textbox or Rich TextBox? In fact, I mean, a part of the text is highlighted by another color differing from the color assigned for text selection. To clarify, I have attached an image showing this behavior. (The image is from a website, not WPF).
The bold and dark green part is a text which is just highlighted, and the gray region is a selected part.
Using the RichTextBox element allows for more styling options which, to my knowledge, aren't available for the regular TextBox element.
Here is an approach that I have created:
// Generate example content
FlowDocument doc = new FlowDocument();
Run runStart = new Run("This is an example of ");
Run runHighlight = new Run("text highlighting in WPF");
Run runEnd = new Run(" using the RichTextBox element.");
// Apply highlight style
runHighlight.FontWeight = FontWeights.Bold;
runHighlight.Background = Brushes.LightGreen;
// Create paragraph
Paragraph paragraph = new Paragraph();
paragraph.Inlines.Add(runStart);
paragraph.Inlines.Add(runHighlight);
paragraph.Inlines.Add(runEnd);
// Add the paragraph to the FlowDocument
doc.Blocks.Add(paragraph);
// Apply to RichTextBox
YourRichTextBoxHere.Document = doc;
View Screenshot
I found this article to be helpful.
Highlight Searched Text in WPF ListView
While the article is about highlighting searched text in a ListView, I have easily adapted it in my own code to work with pretty much any control.
Starting with the control you pass in, it will recursively look for TextBlocks and will find the text you want, extract it as an inline, and will change it's Background / Foreground properties.
You can easily adapt the code to be a behavior if your want.
Here is an example:
private void HighlightText(object controlToHighlight, string textToHighlight)
{
if (controlToHighlight == null) return;
if (controlToHighlight is TextBlock tb)
{
var regex = new Regex("(" + textToHighlight + ")", RegexOptions.IgnoreCase);
if (textToHighlight.Length == 0)
{
var str = tb.Text;
tb.Inlines.Clear();
tb.Inlines.Add(str);
return;
}
var substrings = regex.Split(tb.Text);
tb.Inlines.Clear();
foreach (var item in substrings)
{
if (regex.Match(item).Success)
{
var run = new Run(item)
{
Background = (SolidColorBrush) new BrushConverter().ConvertFrom("#FFFFF45E")
};
tb.Inlines.Add(run);
}
else
{
tb.Inlines.Add(item);
}
}
}
else
{
if (!(controlToHighlight is DependencyObject dependencyObject)) return;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObject); i++)
{
HighlightText(VisualTreeHelper.GetChild(dependencyObject, i), textToHighlight);
}
}
}
I hope this is helpful!

RTF Color Table looping through all colors

I have a RichTextBox. I have added RTF formatting (mainly a color table) to this RichTextBox. When I first Append text to it, it loops through all* the colors of the color table.
*It starts with applying color0, then color1, then color2, etc until all the colors in the color table have been applied OR if the text that is being output has one of those colors already - in that case it stops this "looping" and continues as intended. See screenshot for example.
Here is the code:
private void populateColorCodeDictionary() {
startRTFString = #"{\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\fswiss\fcharset0;}}" +
#"{\colortbl
;
\red0\green0\blue0;
\red170\green0\blue0;
\red0\green170\blue0;
\red128\green128\blue0;
\red0\green0\blue128;
\red128\green0\blue128;
\red0\green128\blue128;
\red127\green127\blue127;
\red85\green85\blue85;
\red255\green0\blue0;
\red0\green255\blue0;
\red255\green255\blue0;
\red0\green0\blue255;
\red255\green0\blue255;
\red0\green255\blue255;
\red255\green255\blue255;
}";
colorCodeDictionary.Add("\x1b[0;30m", #"\cf1");
colorCodeDictionary.Add("\x1b[0;31m", #"\cf2");
colorCodeDictionary.Add("\x1b[0;32m", #"\cf3");
colorCodeDictionary.Add("\x1b[0;33m", #"\cf4");
colorCodeDictionary.Add("\x1b[0;34m", #"\cf5");
colorCodeDictionary.Add("\x1b[0;35m", #"\cf6");
colorCodeDictionary.Add("\x1b[0;36m", #"\cf7");
colorCodeDictionary.Add("\x1b[0;37m", #"\cf8");
colorCodeDictionary.Add("\x1b[1;30m", #"\cf9");
colorCodeDictionary.Add("\x1b[1;31m", #"\cf10");
colorCodeDictionary.Add("\x1b[1;32m", #"\cf11");
colorCodeDictionary.Add("\x1b[1;33m", #"\cf12");
colorCodeDictionary.Add("\x1b[1;34m", #"\cf13");
colorCodeDictionary.Add("\x1b[1;35m", #"\cf14");
colorCodeDictionary.Add("\x1b[1;36m", #"\cf15");
colorCodeDictionary.Add("\x1b[1;37m", #"\cf16");
/*
\x1b[0;30m = cf1 = black
\x1b[0;31m = cf2 = red
\x1b[0;32m = cf3 = green
\x1b[0;33m = cf4 = brown
\x1b[0;34m = cf5 = blue
\x1b[0;35m = cf6 = purple
\x1b[0;36m = cf7 = cyan
\x1b[0;37m = cf8 = gray
\x1b[1;30m = cf9 = darkGray
\x1b[1;31m = cf10 = light Red
\x1b[1;32m = cf11 = light green
\x1b[1;33m = cf12 = yellow
\x1b[1;34m = cf13 = light blue
\x1b[1;35m = cf14 = indigo
\x1b[1;36m = cf15 = light cyan
\x1b[1;37m = cf16 = white
*/
}
The above method sets up the variables. The most interesting part is the startRTFString variable.
private void updateOutputWindow(string text) {
string newText = string.Empty;
if (InvokeRequired) {
Invoke(new MethodInvoker(delegate () {
updateOutputWindow(text);
}));
}
else {
newText = startRTFString;
newText += rtb_outputWindow.Rtf;
newText += replaceAnsiColorCodes(text);
rtb_outputWindow.Rtf = newText;
}
}
The above method outputs text to the RichTextBox.
private string replaceAnsiColorCodes(string inData) {
string returnString = inData;
foreach (KeyValuePair<string, string> entry in colorCodeDictionary) {
returnString = returnString.Replace(entry.Key, entry.Value);
}
returnString = returnString.Replace("\r", #"\line"); //Newline
returnString = returnString.Replace("\x1b[0;1m", ""); //Bold
returnString = returnString.Replace("\x1b[0m", #"\cf16 "); //Reset
return returnString;
}
The above method converts ANSI codes into RTF color codes. (As well as newline and bold. I have chosen to set bold to be nothing, as of now.)
for (int i = 0; i < 15; i++) {
updateOutputWindow("\x1b[0mline" + i.ToString());
}
The above is just a little loop that I run as soon as the application has started. It is for testing purposes and can be seen in the following screenshot:
Above is a screenshot of the issue. As you can see, the first 15 lines of text all have different colors. They should all have the same, default, color (in my case white.) After the "looping" is done, it continues to work as intended; applying the correct color to the text.
It should be noted that the line above the first (line0) is empty. The color table starts with black, so line0 should be black colored, and line1 should be dark red. Don't know why it's not following it's own rules.
Here is another test I ran:
for (int i = 0; i < 15; i++) {
updateOutputWindow("\x1b[0;34mline" + i.ToString()); //This time I changed the color to be dark blue
}
As you can see: the "looping" happened until it ran into the same color, and it kept going with that color.
I have no idea why this happens or what causes it. My guess is that I have messed up the RTF "script" somehow. Does anyone know of a solution? (I don't want ANY of this color table looping to happen. I want it to output the default color, unless there is a color code present - in that case I want that color to be presented.)
EDIT:
I added this bit of code to the method updateOutputWindow MessageBox.Show(newText);. Below is the screenshot of the result:
As you can see from the screenshot above the RichTextBox has some kind of "default RTF code" already in-place. -This default code gets added ontop of my "custom RTF code". It doesn't seem to interfere with the color table, though. (Unless this is what is casing the issue at hand, in that case it most certainly is interfering, but in a very specific, one-time way.)
EDIT #2: If I continue to run this method over and over again, the RTF-code doesn't get added for each additional run. It gets added once (at the very top) and then no more. I think that is a good thing, and I believe it is caused by how RichTextBoxes natively handles RTF code.
You are converting terminal codes (or as you call them ANSI codes) to RTF format. Text that you give to updateOutputWindow contains \x1b[0;34m, but in updateOutputWindow there is no Replace line for that terminal code. You have something similar, but not exactly that. As a result a terminal code is now part of RTF, so who knows what happens. Must deal with all terminal codes (replace or remove).
Also, the following code seems strange to me:
newText = startRTFString;
newText += rtb_outputWindow.Rtf;
newText += replaceAnsiColorCodes(text);
rtb_outputWindow.Rtf = newText;
It seems that rtb_outputWindow.Rtf will grow with every function call, adding new startRTFString every time. Instead of the code above I propose the following (myStuff is a property, like startRTFString):
myStuff = myStuff + replaceAnsiColorCodes(text);
rtb_outputWindow.Rtf = startRTFString + myStuff;

How to restore Caret position or Change color of text without making a selection in RichTextBox

This has been an issue with many of my applications and I don't know why Windows doesn't have an elegant solution for this.
I am working with Winforms in .Net 4.5 in VS2013
For example, I would like to change the color of one line of text in a multiline RichTextBox.
For this I am required to set the selection using something like
rtb.Select(rtb.GetFirstCharIndexFromLine(r), str.Length);
Then, I would set the color using
rtb.SelectionColor = Color.Red;
And presumably, cancel the selection with
rtb.DeselectAll();
Now the problem is the cursor/caret has moved back to the beginning of the line,
I try to fix it by saving the previous Caret Position,
rtb.CaretPosition
However, CaretPosition is not a method of RichTextBox, and everywhere online this is the primary method everyone uses.
I tried adding PresentationFramework to my References and to my code I added
using System.Windows.Framework;
As suggested here: http://msdn.microsoft.com/en-us/library/system.windows.controls.richtextbox.caretposition(v=vs.110).aspx
but I still do not see the CaretPosition property, only the ScrollToCaret() method.
My 2 questions are:
How do I get the CaretPosition property in my RichTextBox?
How can I change the text color without using selections and affecting the caret position, having to write complex logic to restore it for the user.
My application checks serial numbers, one per line, and highlights them red if they do not match the format, as shown below.
private void rtb_TextChanged(object sender, EventArgs e)
{
string pattern = #"[A-Z]{2}[A-Z, 0-9]{2}\d{4}";
Regex rgx = new Regex(pattern, RegexOptions.IgnoreCase);
TextReader read = new System.IO.StringReader(rtb.Text);
SerialNumbers.Clear();
int selectStart = 0;
for (int r = 0; r < rtb.Lines.Length; r++)
{
string str = read.ReadLine();
if (str != null)
{
selectStart += str.Length;
MatchCollection matches = rgx.Matches(str);
if (matches.Count == 1)
{
SerialNumbers.Add(str);
}
else
{
rtb.Select(rtb.GetFirstCharIndexFromLine(r), str.Length);
rtb.SelectionColor = Color.Red;
rtb.DeselectAll();
}
}
}
}
You should be using SelectionCaret (as #Mangist mentioned in a comment) because you're using WinForms and not WPF. The MSDN article you referenced only applies to WPF, which is very different from WinForms.
As an example, I use the following to easily log to a rich text box from anywhere in a WinForms app:
public static void Log(string text, ref RichTextBox rtbLogBox) {
//
if (text == null) return;
var timestamp = DateTime.Now.ToLongTimeString();
var logtext = string.Format("{0} - {1}\r\n\r\n", timestamp, text);
if (rtbLogBox.InvokeRequired) {
var logBox = rtbLogBox;
logBox.Invoke(new MethodInvoker(delegate {
logBox.AppendText(logtext);
logBox.Update();
logBox.SelectionStart = logBox.Text.Length;
logBox.ScrollToCaret();
}));
} else {
rtbLogBox.AppendText(logtext);
rtbLogBox.Update();
rtbLogBox.SelectionStart = rtbLogBox.Text.Length;
rtbLogBox.ScrollToCaret();
}
}
Notice how the ScrollToCaret() is called after setting SelectionStart to the length of text in the rich text box. This solves the 'issue' of AppendText not scrolling to the bottom after adding text.
In your case you will simply want to save the SelectionStart value before you format your text with the highlighting, and then restore it once you've finished.
Fixed it by saving SelectionStart position
int selectionStart = SNbox.SelectionStart;
SNbox.Select(SNbox.GetFirstCharIndexFromLine(r), str.Length);
SNbox.SelectionColor = Color.Red;
SNbox.DeselectAll();
SNbox.SelectionStart = selectionStart;
SNbox.SelectionLength = 0;

C# TreeView OnDrawNode is working slow and creating artifact

I am working on a custom C# TreeView and I would like to do some custom draw to highlight the keywords appear in the name of nodes.
I did :
DrawMode = TreeViewDrawMode.OwnerDrawText;
in the constructor of the custom TreeView and override the OnDrawNode:
protected override void OnDrawNode(DrawTreeNodeEventArgs e)
{
if (!e.Node.IsVisible) { return; }
if (e.Node.Bounds.IsEmpty) { return; }
e.DrawDefault = false;
...draw calls...
But it worked strangely after I coded like this, the perceived behaviors include:
OnDrawNode being call on the child nodes which is not expanded and invisible
When the content of the TreeView updates, the user would see the old content and new content at the same time overlapping with each other. The old content would disappear not until about half second or longer.
The rendering speed is much slower than the original draw call.
Another modification I did is the code snippet I found here to suppress the flickering happens when the TreeView is updating:
http://dev.nomad-net.info/articles/double-buffered-tree-and-list-views
But it seems not directly related to the problem since I can still see the text overlapping after removing it.
I wonder if anyone have any idea about this issue?
Any thought would be appreciated.
Thank you.
edit:
The content of the OnDrawNode is like:
string pattern = keyword;
if (!string.IsNullOrWhiteSpace(pattern))
{
Regex regularExpressionnew = Regex(pattern);
Match match = regularExpression.Match(e.Node.Text);
while (match.Success)
{
CaptureCollection captureCollection = match.Groups[0].Captures;
foreach (Capture capture in captureCollection)
{
int highlightStartIndex = capture.Index;
int highlightEndIndex = capture.Index + pattern.Length;
e.Graphics.FillRectangle(nodeHightLightColor, GetTextBoundsBetweenIndex(e.Graphics, e.Node.Text, highlightStartIndex, highlightEndIndex, e.Bounds));
}
match = match.NextMatch();
}
Brush drawBrush = new SolidBrush(Color.Black);
e.Graphics.DrawString(e.Node.Text, Font, drawBrush, e.Bounds);
GetTextBoundsBetweenIndex is essentially calculating the square area covering the characters between highlightStartIndex and highlightEndIndex.
But the lag and overlap would happen event the regular expression is commented out and only the text rendering left.

things on the usage of the richtextbox in windows phone

I'm using the richtextbox to show some Html content in windows phone 7.1.
The html source-code is like:
Paragraph1</p>
<img src="http://www.ifanr.com/wp-content/uploads/2011/11/DSC_332401.jpg" alt="" width="600" height="338" /></p>
Paragraph2。</p>
<h3>Title h3</h3>
Paragraph3。
</p>
Then I use the
"string[] sArray = Regex.Split(html, "</p>", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);"
to split them into a Array. Finally, I use the code:
foreach (string array in sArray)
{
Paragraph parag = new Paragraph();
Run run = new Run();
Bold bold = new Bold();
if (!Regex.IsMatch(array.ToString(), #"<img\b[^<>]*?\bsrc\s*=\s*[""']?\s*(?<imgUrl>[^\s""'<>]*)[^<>]*?/?\s*>"))
{
//h3
if (array.ToString().Contains("</h3>"))
{
string hString = array.ToString();
hString = Regex.Replace(hString, "<h3>", "");
string[] hArray = Regex.Split(hString, "</h3>", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
bold.Inlines.Add(hArray[0].ToString());
parag.Inlines.Add(bold);
run.Text = hArray[1].ToString();
parag.Inlines.Add(run);
}
else
{
if(array.ToString().Contains("<blockquote>"))
{
run.Text = Regex.Replace(array.ToString(), "<blockquote>", "blockquote:");
run.FontSize = 18;
}
else
run.Text = array.ToString();
parag.Inlines.Add(run);
}
rtb.Blocks.Add(parag);
}
else
{
//insert the image into richtextbox
Regex regImg = new Regex(#"http://[^\[^>]*?(gif|jpg|png|jpeg|bmp|bmp)", RegexOptions.IgnoreCase);
MatchCollection matches = regImg.Matches(array.ToString());
string result = null;
foreach (Match match in matches)
result = match.Value;
Image image = new Image();
image.Stretch = Stretch.Uniform;
image.Source = new BitmapImage(new Uri(result, UriKind.RelativeOrAbsolute));
InlineUIContainer iuc = new InlineUIContainer();
iuc.Child = image;
parag.Inlines.Add(iuc);
rtb.Blocks.Add(parag);
}
to add some Paragraph or images into the richtextbox, everything goes well in the beginning, but when I Scroll down the richtextbox, the rest paragraph disappear. It confused me all day long, as I could't find out what's wrong with the richtextbox.
Is it just a bug in Windows phone? Any thoughts?
ScreenShot1:
ScreenShot2:
p.s:it doesn't matter whether the html source-code contains some non-english characters or not. This happens when the html source-code is in a large amount of words. These two ScreenShots just show the problem.
The phone applies a restriction that any UIElement can't be larger than 2048 pixels in any direction. This is enforced to avoid performance issues relating to memory and having to draw very large objects. This is to protect you from doing something that greatly affects performance but also has some other reasoning behind it. For example, a phone is a poor device for reading large pieces of text. This applies even more so for dense bodies of text. This size restriction therefore forces you to think about how, or if you should, display large pieces of text within your application.
There are some solutions though.
Rather than using a single Paragrpah or TextBlock for a large "unit" of text, you could consider using something like this: http://blogs.msdn.com/b/priozersk/archive/2010/09/08/creating-scrollable-textblock-for-wp7.aspx

Categories

Resources