[Winform] 在 RichTextBox 中著色的偷懶小技巧

在寫 Windows Form 時, 如果不花錢的話, 那麼除了 TextBox 這個文字控制項, 就只剩 RichTextBox 可用了。可惜的是, 雖然都是 RichTextBox, Winform 裡的 RichTextBox 卻不像 WPF 的 RichTextBox 那樣可以賦予方便的屬性。因此, 即使只是對 RichTextBox 裡的文字做著色這般簡單的動作, 都不見得容易, 也不直覺。例如, 我需要在 RichTextBox 裡把某些文字以紅色標示; 如果是網頁程式的話...

在寫 Windows Form 時, 如果不花錢的話, 那麼除了 TextBox 這個文字控制項, 就只剩 RichTextBox 可用了。可惜的是, 雖然都是 RichTextBox, Winform 裡的 RichTextBox 卻不像 WPF 的 RichTextBox 那樣可以賦予方便的屬性。因此, 即使只是對 RichTextBox 裡的文字做著色這般簡單的動作, 都不見得容易, 也不直覺

例如, 我需要在 RichTextBox 裡把某些文字以紅色標示; 如果是網頁程式的話, 我只要在文字左右加上 <span> 標籤, 再賦予樣式 (例如寫作 "<span class="Red">紅字</span>") 即可。但是在 Winform 裡, 並沒有辦法這麼做。我必須先把這些文字左右加上自訂的標籤 (例如 "<red>紅字</red>"), 事後再利用 SelectionColor 予以著色, 相當複雜而且麻煩。更糟糕的是, 如果 RichTextBox 中文字過多 (例如超過 1,500 行), 那麼這個著色的效能會變得奇差無比, 我們甚至能看到它一行一行執行著轉換變色的動作 (也許有人反而覺得這樣蠻酷的)。結果, 等它做完這種轉換的動作, 已經花掉幾十秒了。我曾經仔細算過, 若使用 3,300 行左右的文字, 其中大約有五百個需要標色的地方, 它會花掉大約 35 秒左右。這個動作無法使用平行運算, 所以很難有讓它加速的方法。

在不得已情況下, 我很不甘心地去稍為了解了 RichText 規則 (之所以說「不甘心」, 是因為我覺得未來的文字格式一定是 tag-based 的天下, 不會是 RichText)。然後找到了一個堪稱為投機取巧的方法。

如果你對原理有興趣的話, 首先, 你可能需要對於 RichText 格式中的 "Color Table" 主題稍加了解。在 RichText 規格裡, 文件可以附加上調色盤, 方便我們在 RTF 文件裡快速套用。不過, 如果你把上述連結中的文件稍為往下拉, 你會看到一個 "Style Sheet" 主題, 它提供了更大的彈性。不過我在本文中只會用到 "Color Table", 所以不會花時間在 "Style Sheet" 上面。

不管如何, 我們必須在 RichTextBox 的 RTF 內容中加上一個只有一個顏色的 Color Table, 然後我們就能把這個顏色套用在我們指定的文字上。

一開始, 我們必須讓這個 RichTextBox 控制項 (假設這個控制項叫做 "rtControl") 裡有文字。如此, 我們就可以以 rtControl.Rtf 取出這個 RTF 文件的內容。不過, 如同我在前面提到的, 你必須自己把想要標色的文字包上 <red> ... 和 </red> 這樣的標籤。當然, 你可以不要使用這種標籤, 你可以自己設計, 只要不跟其它文字衝突就行。而且最好不要使用中文 (因為中文會被編碼)。

接著, 我們要把這個 Color Table 塞到 RTF 文件內容裡面。坦白說, 我使用了偷懶的方法, 把這個 Color Table 硬塞入既有的文件內容:

string color = "{\\colortbl ;\\red255\\green0\\blue0;}";
string rtf = rtControl.Rtf;
rtf = rtf.Replace("\\viewkind4", color + "\\viewkind4");

也許你已經注意到, 在 RichText 規格中, 它的紅藍綠三色的值是從 0 到 255; "red255" 是純紅色, 而 "green0" 和 "blue0" 則代表無色。這種值和網頁顏色的標註習慣剛好相反, 使用時可以稍加留意。

在上面三行程式中, 我們把一個 Color Table 塞進 RTF 文件裡。不過你必須確定這個 RTF 文件裡原本並沒有 Color Table 才行。如果你無意間塞入了兩個 Color Table, 那麼只有第一個會生效。

接著, 我們只剩一件事情要做, 那就是把 "<red>" 改成 "\cf1", 把 "</red>" 改成 "\cf0":

string colorStartTag = "<red>";
string colorEndTag= "</red>";
rtControl.Rtf = rtf.
    Replace(colorStartTag + colorEndTag, string.Empty).
    Replace(colorStartTag, "\\cf1").
    Replace(colorEndTag, "\\cf0");

在 RTF 文件中, 從 "\cf1" 到 "\cf0" 中間的區段會被套用 Color Table 裡註冊的第一個顏色。所以, 同理, 你可以在 Color Table 裡加上其它或多個顏色, 然後運用上面的方法為文字著色。

而上面這段程式的重點, 在於我們使用了 String.Replace 方法來取代原有的套用 SelectionColor 方法。原本我們必須先使用 rtControl.Find 方法在 rtControl.Text 中找到 "<red>", 再找到  "</red>", 然後把那個區段選擇起來, 然後套用 SelectionColor 為紅色。但是如果採用如上的 String.Replace 方法, 速度足足快了六倍! 這還只是在文字為 3,300 行的情況。如果文字愈長, 差距就愈明顯。


Dev 2Share @ 點部落