前言
很多IDE或者开发工具中都有语法着色的功能,这是如何实现的呢?笔者试着用C#做了一个Sample,基本上实现此功能。本文一半是原创,一半是参考国外的论坛
思路
语法着色器,实际上只做了两件事情:“接受用户输入”以及“改变关键字字体属性”。1)首先看第一个:接受用户输入。
C#自带的控件中能够满足需求的首推“RichTextBox”,笔者在网上经常看到很多同行都以此为基础,制作出类似MS-Word的种种效果。既然,复杂的Word效果都能够实现,那么语法着色自然也就不在话下。2)其次,最重要的“改变关键字字体属性”。
这一点还可分成三个更小的工作:关键字列表、判断关键字、定义字体属性。这里以基本SQL语句为例。
private IList<String> _KeyWords = new List<String>() { "SELECT", "UPDATE", "DELETE", "INSERT", "DROP", "FROM", "WHERE", "ALERT", "CREATE", "TABLE", "VALUES", "NULL", "AND", "OR", "NOT", "IN", "INTO", "UNION", "AS", "IS", "COUNT", "MAX", "MIN", "AVG", "WAIT", "NOWAIT", "WITHOUT", "LOCK", "GROUP", "ORDER", "BY", "JOIN", "LEFT", "RIGHT", "CASE", "WHEN", "ELSE", "END", "COMMIT", "ROLLBACK" };
为了在用户输入时能够立即对所输入的内容有所反映,我们需要override一下RichTextBox的OnTextChanged()方法。
其他属性及方法介绍请参考MSDN
笔者的算法很简单,遍历业已定义好的关键字列表,用其与用户输入的内容进行比较,如果发现相同,则按照字符的index值计算编辑区域。
在C#中有一个叫做FONT的类能够满足需求。 其他属性及方法介绍请参考MSDN
注意事项
笔者在尝试的过程中发现屏幕中显示的内容会随着用户的输入,出现闪烁。究其原因可能是因为Windows一直在重新绘制窗口造成的,为了回避这个问题,我们可以使用SuspendLayout()和ResumeLayout()暂停和恢复画面重新绘制的过程。做完这几步,我们的语法着色控件也就大功告成了。下面是全部源代码。在VS2008 Express版本上调试通过。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; using System.Text.RegularExpressions; using System.Drawing; using System.ComponentModel; namespace Base { public partial class SQLEditor : RichTextBox { /// <summary> /// 关键字列表 /// </summary> private IList<String> _KeyWords = new List<String>() { "SELECT", "UPDATE", "DELETE", "INSERT", "DROP", "FROM", "WHERE", "ALERT", "CREATE", "TABLE", "VALUES", "NULL", "AND", "OR", "NOT", "IN", "INTO", "UNION", "AS", "IS", "COUNT", "MAX", "MIN", "AVG", "WAIT", "NOWAIT", "WITHOUT", "LOCK", "GROUP", "ORDER", "BY", "JOIN", "LEFT", "RIGHT", "CASE", "WHEN", "ELSE", "END", "COMMIT", "ROLLBACK" }; [CategoryAttribute("KeyWords"), Description("关键字列表")] public IList<String> KeyWords { get { return this._KeyWords; } set { this._KeyWords = value; } } /// <summary> /// 关键字颜色(默认为蓝色) /// </summary> private Color _KeyWordsColor = Color.Blue; [CategoryAttribute("KeyWordsColor"), Description("关键字颜色")] public Color KeyWordsColor { get { return this._KeyWordsColor; } set { this._KeyWordsColor = value; } } // TODO 数字,字符串属性 /// <summary> /// 分隔符 /// </summary> Regex tokens = new Regex(@"[\s\r\n\t\(\)\;]"); /// <summary> /// 接受输入时语法着色 /// </summary> /// <param name="e"></param> protected override void OnTextChanged(EventArgs e) { base.OnTextChanged(e); // TODO DEBUG Console.WriteLine("POINT:" + this.SelectionStart + "|" + "SIZE:" + this.Text.Length); // 暂停屏幕刷新,防止闪烁 SuspendLayout(); // 缓存输入焦点位置 int inputPoint = this.SelectionStart; String[] wrk_words = tokens.Split(this.Text.ToUpper()); // 判断是否有必要进行着色 if (this.Text.Length == 0) return; // 克隆关键字列表 IList<String> C_KeyWords = new List<String>(this._KeyWords); // 关键字开始坐标 int iStart = 0; // 初始化 this.SelectionStart = 0; this.SelectionLength = this.Text.Length; this.SelectionColor = Color.Black; this.SelectionFont = new Font("Courier New", 10, FontStyle.Regular); // 逐字匹配 foreach (String word in wrk_words) { foreach (String keyw in C_KeyWords) { // 判断当前值是否为关键字 if (word.Equals(keyw)) { // 查找坐标 iStart = this.Text.ToUpper().IndexOf(keyw); // 判断是否存在处理对象 while (iStart >= 0) { this.SelectionStart = iStart; this.SelectionLength = keyw.Length; this.SelectionColor = Color.Blue; this.SelectionFont = new Font("Courier New", 10, FontStyle.Bold); // 获取下一次处理开始坐标 iStart = this.Text.ToUpper().IndexOf(keyw, this.SelectionStart + 1); this.SelectionStart = this.Text.Length; this.SelectionLength = 0; } } // break; } } // 计算输入点的位置,如果不在末尾,则说明用户在修改前面的内容,否则将焦点移到末尾 this.SelectionStart = (inputPoint < this.Text.Length) ? inputPoint : this.Text.Length; this.SelectionLength = 0; ResumeLayout(); } } }