Yes, it is possible and about twice as fast than the 'regular' way..:
Changing 30k words in a 3M text takes 28 seconds over 60 seconds before..
Here are the steps I would recommend, assuming your words are identifyable in the richTextBox.Rtf
(*):
You could create your own color table, but it seems safer to let the system do it for you: I cheat by coloring the 1st letter before and resetting it after coloring the matches..
I pre- and postfix the search word by the rtf code for a foreground color index into the table. My code assumes that there is only one extra color in addition to the default one.
If you have more you should keep track and/ or analyze the colortable..
Here is a RTF reference, btw..
I do the replacement with the RegEx
in my RichTextBox RTB
like this:
string search "find me!";
RTB.SelectionStart = 0;
RTB.SelectionLength = 1;
RTB.SelectionColor = Color.HotPink;
Regex RX = new Regex(search);
MatchCollection matches = RX.Matches(RTB.Rtf);
RTB.Rtf = RX.Replace(RTB.Rtf, "\\cf1 " + search + "\\cf0 ");
RTB.SelectionStart = 0;
RTB.SelectionLength = 1;
RTB.SelectionColor = RTB.ForeColor;
(*) Note that modifying the Rtf
property like this assumes that your search texts are identifiable in the Rtf
. You can and should check this by comparing the matches count when searching the Rtf
and the Text
! when they don't agree you probably need to use the 'regular' way..
Note that this only deals with Colors
. For Font
sizes etc you will have to add \fn
(which index into the stylesheet) commands in a similar way..
Update: I have wrapped the code above in an expanded function, also taking care of more colors, word boundaries and some checks..:

int colorWords(RichTextBox RTB, String searchWord, Color color)
{
string wordChar = @"\w*"; // or @"\b*" for stricter search
Regex RX = new Regex(wordChar + searchWord + wordChar);
RTB.SelectionStart = 0;
RTB.SelectionLength = 0;
RTB.SelectedText = "~"; // insert a dummy character
RTB.SelectionStart = 0;
RTB.SelectionLength = 1;
RTB.SelectionColor = color; // and color it
MatchCollection matches = null;
matches = RX.Matches(RTB.Text);
int textCount = matches.Count;
matches = RX.Matches(RTB.Rtf);
// we should not find more in the rtf code, less is ok
if (textCount < matches.Count) return -1;
if (matches.Count <= 0) return 0;
List<Color> colors = getRtfColorTable(RTB);
int cIndex = 1;
Color cRGB = Color.FromArgb(255, color);
if (colors.Contains(cRGB) )
cIndex = colors.FindIndex(x => x == cRGB) + 1;
RTB.Rtf = RX.Replace(RTB.Rtf, "\\cf" + cIndex + " " + searchWord + "\\cf0 ");
RTB.SelectionStart = 0;
RTB.SelectionLength = 1;
RTB.Cut(); // remove the dummy
return matches.Count;
}
Here is a function that pulls out the current colors from the Rtf color table. (Hopefully, the full spec is not exactly very small and tackling it with two simple IndexOf
is a little optimistic.. ;-)
List<Color> getRtfColorTable(RichTextBox RTB)
{ // \red255\green0\blue0;
List<Color> colors = new List<Color>();
string tabString = @"\colortbl ;";
int ct0 = RTB.Rtf.IndexOf(tabString);
if (ct0 >= 0)
{
ct0 += tabString.Length;
int ct1 = RTB.Rtf.IndexOf(@"}", ct0);
var table = RTB.Rtf.Substring(ct0, ct1 - ct0).Split(';');
foreach(string t in table)
{
var ch = t.Split('\\');
if (ch.Length == 4)
{
int r = Convert.ToInt16(ch[1].Replace("red", ""));
int g = Convert.ToInt16(ch[2].Replace("green", ""));
int b = Convert.ToInt16(ch[3].Replace("blue", ""));
colors.Add(Color.FromArgb(255, r, g, b));
}
}
}
return colors;
}
The example was called like this:
colorWords(RTB, "<DIR>", Color.SaddleBrown);
colorWords(RTB, "Verzeichnis", Color.BlueViolet);
colorWords(RTB, "2012", Color.OrangeRed);