更改所选RTF文本

3

我有一个richTextBox和一个包含某些单词的正则表达式。一旦找到所有想要更改颜色为蓝色的单词,我可以使用SelectionColor = Blue,但是当需要对成千上万个单词进行着色时,速度变得相当慢。

经过一些搜索,我发现更改richTextBox的RTF是更快地更改文本(例如大小和/或颜色)的方法。

这是我的未完成代码:

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 = Blue 会将 SelectedRtf 更改为:
{\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。然而,尝试后似乎什么也没有发生。字的颜色仍然保持不变。有什么想法吗?

我不知道如何直接修改RTF。 然而,作为替代方法,请考虑使用您的旧方法,但使用WM_SETREDRAW暂时关闭更新,就像这个问题中所述。 因此,先关闭它,使用循环,然后再打开并刷新RTB。 - Idle_Mind
至少将代码包围在Suspend-和ResumeLayout中什么也没做。 - TaW
1个回答

2

是的,这是可能的,并且比“常规”方法快两倍左右。

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

以下是我建议的步骤,假设您的单词可以在richTextBox.Rtf中被识别(*):

  • 您可以创建自己的颜色表,但让系统为您完成似乎更加安全:我通过在着色匹配之前着色第一个字母并在之后重置它来作弊。

  • 我在搜索词前缀和后缀处添加了rtf代码,用于将前景色索引到表格中。我的代码假定除默认颜色外只有一种额外颜色。

  • 如果您有更多,请跟踪和/或分析颜色表。

顺便提一下,这里有一个RTF参考

我使用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 的匹配计数以检查此内容!当它们不一致时,您可能需要使用“常规”方法。
请注意,此仅处理 Colors。对于字体大小等,您需要以类似的方式添加\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颜色表中提取当前的颜色。(希望完整规范不是非常小,用两个简单的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);

嗯,昨晚试了一下,但好像没起作用。我可能做错了什么...等我搞定了再告诉你。如果它真的比你说的快两倍,那正是我想要的! - OC_
其实,忘记我刚才说的话吧。我已经成功让它适用于一种颜色和一个单词。下一步是同一种颜色适用于多个单词。我想我会将该代码放入循环中,并用我要查找的单词替换“search”字符串的值... - OC_
1
是的,就是这样。我已经将代码封装在一个函数中,你可能会觉得很有用。还有一个更新,插入一个虚拟字符来处理第一个匹配项在开头的情况... - TaW
1
抱歉回复晚了,我一直很忙,但最终我测试了多个单词和颜色,效果非常好。你不仅给出了一个优秀的答案,还帮助我让它适用于多种颜色。感谢你的时间。 - OC_

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接