改变选择的RTF

Change SelectedRTF

我有一个 richTextBox 和一个带有一些单词的正则表达式。有一次,我找到了所有我想把它们的颜色改成蓝色的词。我可以使用 SelectionColor = Blue,但是当涉及到为数千个单词着色时,它变得非常慢。

经过一些搜索,我了解到更改 richTextBox 的 RTF 是更改文本的更快方法(例如它的大小 and/or 颜色)。

这是我未完成的代码:

MatchCollection matches = myRegex.Matches(richTextBox.text);
foreach (Match match in matches)
{
    richTextBox.Select(match.Index, match.Length);
    string addColor = @"{\colortbl ;\red0\green0\blue255;}" + Environment.NewLine;

    richTextBox.SelectionColor = Color.Blue; //Must be replaced
}

我还发现在每种情况下(在我的情况下,整个文本使用相同的字体和相同的大小,只有一些单词的颜色发生变化)SelectedRtf 是:

{\rtf1\ansi\deff0{\fonttbl{\f0\fnil\fcharset0 Consolas;}}
\uc1\pard\lang1033\f0\fs18 word} // richTextBox.SelectedRtf

此外,使用 Selection.Color = BlueSelectedRtf 更改为:

{\rtf1\ansi\deff0{\fonttbl{\f0\fnil\fcharset0 Consolas;}}
{\colortbl ;\red0\green0\blue255;}  // The addColor string!
\uc1\pard\lang1033\f0\fs18 word}

为了得到上面的字符串,我使用了这个:richTextBox.SelectedRtf.Insert(59, addColor),所以我需要做的就是用它替换 SelectedRtf。然而,经过一些尝试,似乎什么也没有发生。单词的颜色保持不变。有什么想法吗?

是的,这是可能的,而且比 'regular' 方式快两倍..:[=​​27=]

更改 3M 文本中的 30k 个单词需要 28 秒,而之前的 60 秒..

以下是我推荐的步骤,假设您的话在 richTextBox.Rtf (*) 中是 可识别

  • 你可以创建你自己的颜色table,但让系统为你做似乎更安全:我作弊的方法是先给第一个字母上色,然后在给火柴上色后重置它..

  • 我通过前景色索引的 rtf 代码将搜索词前后添加到 table 中。我的代码假设除了默认颜色之外只有一种额外的颜色。

  • 如果你有更多,你应该跟踪和/或分析颜色table..

这是一个 RTF reference,顺便说一句..

我用 RichTextBox RTB 中的 RegEx 进行替换,如下所示:

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;

(*) 请注意,像这样修改 Rtf 属性 假设您的搜索文本是 可识别的 Rtf 中。您可以而且应该通过在搜索 RtfText 时比较匹配计数来检查这一点!当他们不同意时,您可能需要使用 'regular' 方式..

请注意,这仅涉及 Colors。对于 Font 大小等,您必须以类似的方式添加 \fn(样式表中的索引)命令..

更新: 我将上面的代码包装在一个扩展函数中,同时处理了更多的颜色、单词边界和一些检查..:

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;
}

这是一个从 Rtf 颜色中提取当前颜色的函数 table。 (希望完整的规范不是非常小,用两个简单的 IndexOf 来解决它有点乐观..;-)

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;
}

这个例子是这样调用的:

colorWords(RTB, "<DIR>", Color.SaddleBrown);
colorWords(RTB, "Verzeichnis", Color.BlueViolet);
colorWords(RTB, "2012", Color.OrangeRed);