摘要:利用 Regular Expression 為 RichTextBox 加上 syn利用 Regular Expression 為 RichTextBox 加上 syntax highlighting 功能tax highlighting 功能
最近在設計的應用程式,需要一個有 syntax highlighting 功能的 RichTextBox。後來在 Code Project 上面找到一個陽春的 RichTextBox 元件,這個元件可以針對字串、整數...等各種語法文字顯示自訂的顏色。不過我的需求更陽春,我只需要把類似 XML 的標籤用特殊顏色顯示就行了,像這樣:
因 此自己動手修改了一下,把程式碼簡化,並將類別名稱改為 RichTextBoxHL,再加個小圖示,以便安裝到 VS2005 的 Toolbox 時有個比較像樣的圖案。原始程式碼附在最後。這當中為了尋找成對的起始標籤和結束標籤,又 Goggle 了一下,看看有沒有現成的 regular expression,結果不只找到了:"<(?<tag>\\w*)>(?<text>.*)</\\k<tag>>",還發現另一個好用的 Regular Expression 的編輯與測試工具:Expresso。雖然早知道 regular expression 強大好用,但怎麼也耐不住性子去學那一大串的語法符號。有了這個工具,倒是增加不少學習和使用上的方便。另外,RegExpLib.com 也是可以去尋寶的地方。
RichTextBoxHL 原始程式碼:
using System; using System.ComponentModel; using System.Collections.Generic; using System.IO; using System.Text; using System.Drawing; using System.Windows.Forms; using System.Text.RegularExpressions; using Huanlin.Helpers;
namespace Huanlin.WinForms { /// <summary> /// 能夠將標籤以特殊顏色顯示的 RichTextBox。 /// 此元件參考自 Patrik Svensson 的文章:Enabling syntax highlighting in a RichTextBox。 /// URL: http://www.codeproject.com/cs/miscctrl/SyntaxRichTextBox.asp。 /// </summary> [ToolboxBitmap(typeof(RichTextBoxHL), "RichTextBoxHL.bmp")] public class RichTextBoxHL : RichTextBox { private bool m_SkipTextChanged; private bool m_EnableUpdate; private string m_Line; private int m_ContentLength; private int m_LineStartIndex; private int m_LineEndIndex; private int m_LineLength; private int m_CurrentSelection;
private bool m_EnableTagColor; private Color m_TagColor;
public RichTextBoxHL() : base() { m_EnableUpdate = true; m_SkipTextChanged = false;
m_EnableTagColor = true; m_TagColor = Color.Maroon; }
public new void LoadFile(string path) { m_SkipTextChanged = true; try { base.LoadFile(path); ProcessAllLines(); } finally { m_SkipTextChanged = false; } }
public new void LoadFile(string path, RichTextBoxStreamType fileType) { m_SkipTextChanged = true; try { base.LoadFile(path, fileType); ProcessAllLines(); } finally { m_SkipTextChanged = false; } }
public new void LoadFile(Stream data, RichTextBoxStreamType fileType) { m_SkipTextChanged = true; try { base.LoadFile(data, fileType); ProcessAllLines(); } finally { m_SkipTextChanged = false; } }
protected override void WndProc(ref Message m) { if (m.Msg == 0x00f) { if (m_EnableUpdate) base.WndProc(ref m); else m.Result = IntPtr.Zero; } else base.WndProc(ref m); }
protected override void OnTextChanged(EventArgs e) { if (m_SkipTextChanged) return; m_EnableUpdate = false;
try { // Calculate sh*t here. m_ContentLength = this.TextLength;
int currSelectionStart = SelectionStart; int currSelectionLength = SelectionLength;
// Find the start of the current line. m_LineStartIndex = currSelectionStart; while ((m_LineStartIndex > 0) && (Text[m_LineStartIndex - 1] != '\n')) m_LineStartIndex--;
// Find the end of the current line. m_LineEndIndex = currSelectionStart; while ((m_LineEndIndex < Text.Length) && (Text[m_LineEndIndex] != '\n')) m_LineEndIndex++;
// Calculate the length of the line. m_LineLength = m_LineEndIndex - m_LineStartIndex;
// Get the current line. m_Line = Text.Substring(m_LineStartIndex, m_LineLength);
ProcessLine(); } finally { m_EnableUpdate = true; } }
private void ProcessLine() { // Save the position and make the whole line black int pos = base.SelectionStart; base.SelectionStart = m_LineStartIndex; base.SelectionLength = m_Line.Length; base.SelectionColor = Color.Black;
ProcessTagColor();
base.SelectionStart = pos; base.SelectionLength = 0; base.SelectionColor = Color.Black;
m_CurrentSelection = pos; }
private void ProcessTagColor() { if (!m_EnableTagColor) return;
MatchCollection matches = StrHelper.FindTagPairs(m_Line);
int start; int end;
foreach (Match match in matches) { // 每個找到的 Match 物件都包含一對標籤: <xxx>abc</xxx>
// 起始標籤 start = match.Index; end = m_Line.IndexOf('>', start + 1); base.SelectionStart = m_LineStartIndex + start; base.SelectionLength = end - start + 1; base.SelectionColor = m_TagColor;
// 結束標籤 start = m_Line.IndexOf('<', end + 1); end = m_Line.IndexOf('>', start + 1); base.SelectionStart = m_LineStartIndex + start; base.SelectionLength = end - start + 1; base.SelectionColor = m_TagColor; } }
private void ProcessAllLines() { m_Line = base.Text; m_LineStartIndex = 0; m_LineEndIndex = m_LineStartIndex + m_Line.Length;
ProcessLine(); }
#region 屬性
public new string Text { get { return base.Text; } set { m_SkipTextChanged = true; try { base.Text = value; ProcessAllLines(); } finally { m_SkipTextChanged = false; } } }
public new string[] Lines { get { return base.Lines; } set { m_SkipTextChanged = true; try { base.Lines = value; ProcessAllLines(); } finally { m_SkipTextChanged = false; } } }
[Browsable(true), Description("是否將標籤顯示成特殊顏色。")] public bool EnableTagColor { get { return m_EnableTagColor; } set { if (m_EnableTagColor != value) { m_EnableTagColor = value; ProcessAllLines(); } } }
[Browsable(true), Description("標籤顏色。")] public Color TagColor { get { return m_TagColor; } set { if (m_TagColor != value) { m_TagColor = value; ProcessAllLines(); } } }
#endregion } }
其中呼叫到的 StrHelper.FindTagPairs() 程式碼為:
public static MatchCollection FindTagPairs(string s)
{
string pattern = "<(?<tag>\\w*)>(?<text>.*)</\\k<tag>>";
return Regex.Matches(s, pattern);
}
如果需要為讓關鍵字或語法顯示特殊顏色,只要修改 ProcessLine(),利用 regular expression 把找到的字串"上色"就行了(就像上面的 ProcessTagColor 方法一樣)。