因为工作需要,用c#实现了一个能够对vbscript,c#,j#,sql显示语法高亮的文本编辑控件。这里详细介绍一下它的原理。
该控件是从RichTextBox继承下来,以xml格式存储各种语言的关键字。然后重写RichTextBox的OnTextChanged方法,在该方法中对输入文本进行解析,并对关键字进行着色。源代码点击这里下载。
xml文件格式如下,这里仅以j#为例。caseSensitive代表该语言是否大小写敏感。当然,由于本人懒惰成性,关键字是从网上搜集别人整理好的,如有遗漏,概不负责:)
如果需要解析其他语言,请添加相应的xml文件,并修改枚举类型Languages以及Parser类的构造函数中的相应代码。已知bug:当两个词是由括号分割的时候,程序无法识别。比如Function foo(integer i),程序会把foo(integer当作一个词。当然这里有两个解决办法,一个是程序自动进行语法排版,在括号前后自动插入空格;另一个是对括号进行解析。也许以后有空的时候我会加上。
<?xml version="1.0" encoding="utf-8" ?> <definition name="J#" caseSensitive="true"> <word>private</word> <word>protected</word> <word>public</word> <word>namespace</word> <word>class</word> <word>var</word> <word>for</word> <word>if</word> <word>else</word> <word>while</word> <word>switch</word> <word>case</word> <word>using</word> <word>get</word> <word>return</word> <word>null</word> <word>void</word> <word>int</word> <word>string</word> <word>float</word> <word>this</word> <word>set</word> <word>new</word> <word>true</word> <word>false</word> <word>const</word> <word>static</word> <word>package</word> <word>function,</word> <word>internal</word> <word>extends</word> <word>super</word> <word>import</word> <word>default</word> <word>break</word> <word>try</word> <word>catch</word> <word>finally</word> <word>+</word> <word>-</word> <word>=</word> </definition>
Parser类是负责对xml流进行解析,并包含一个方法来判断一个字符串是不是关键字。详细的代码和注释如下:
using System; using System.Xml; using System.IO; using System.Collections; using System.Reflection;
namespace SyntaxEditor { /// <summary> /// Parser 的摘要说明。 /// </summary> public class Parser { private XmlDocument xd=null; private ArrayList al=null; //对xml流解析后,会把每一个关键字字符串放入这个容器中 private bool caseSensitive=false; //记录当前语言大小写敏感否
internal Parser(SyntaxEditor.Languages language) //构造函数接受一个枚举变量 { // // TODO: 在此处添加构造函数逻辑 // Assembly asm = Assembly.GetExecutingAssembly(); string filename=""; switch(language) //取得xml文件名 { case SyntaxEditor.Languages.CSHARP: filename="csharp.xml"; break; case SyntaxEditor.Languages.JSHARP: filename="jsharp.xml"; break; case SyntaxEditor.Languages.SQL: filename="sql.xml"; break; case SyntaxEditor.Languages.VBSCRIPT: filename="vbscript.xml"; break; default: break; }
Stream strm = asm.GetManifestResourceStream(asm.GetName().Name + "."+filename); //取得xml流
//Reads the contents of the embedded file. StreamReader reader= new StreamReader(strm); //下面的代码解析xml流 xd=new XmlDocument(); xd.Load(reader); al=new ArrayList();
XmlElement root=xd.DocumentElement; XmlNodeList xnl=root.SelectNodes("/definition/word"); for(int i=0;i<xnl.Count;i++) { al.Add(xnl[i].ChildNodes[0].Value); }
this.caseSensitive=bool.Parse(root.Attributes["caseSensitive"].Value); }
public bool IsKeyWord(string word) //判断字符串是否为关键字 { bool rtn=false; for(int i=0;i<al.Count;i++) { if(string.Compare(word,al[i].ToString(),!caseSensitive)==0) { rtn=true; break; } } return rtn; } } }
控件类代码如下。
using System; using System.Collections; using System.ComponentModel; using System.Drawing; using System.Data; using System.Windows.Forms; using System.Runtime.InteropServices;
using HWND = System.IntPtr;
namespace SyntaxEditor { /// <summary> /// UserControl1 的摘要说明。 /// </summary> public class SyntaxEditor : System.Windows.Forms.RichTextBox { /// <summary> /// 必需的设计器变量。 /// </summary> private System.ComponentModel.Container components = null;
//使用win32api:SendMessage来防止控件着色时的闪烁现象
[DllImport("user32")] private static extern int SendMessage(HWND hwnd, int wMsg, int wParam, IntPtr lParam); private const int WM_SETREDRAW = 0xB;
public SyntaxEditor() { // 该调用是 Windows.Forms 窗体设计器所必需的。 InitializeComponent(); base.WordWrap=false; // TODO: 在 InitComponent 调用后添加任何初始化
}
/// <summary> /// 清理所有正在使用的资源。 /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if( components != null ) components.Dispose(); } base.Dispose( disposing ); }
#region 组件设计器生成的代码 /// <summary> /// 设计器支持所需的方法 - 不要使用代码编辑器 /// 修改此方法的内容。 /// </summary> private void InitializeComponent() { // // SyntaxEditor // this.Name = "SyntaxEditor";
} #endregion
//重写基类的OnTextChanged方法。为了提高效率,程序是对当前文本插入点所在行进行扫描,
//以空格为分割符,判断每个单词是否为关键字,并进行着色。
protected override void OnTextChanged(EventArgs e) { if(base.Text!="") { int selectStart=base.SelectionStart; int line=base.GetLineFromCharIndex(selectStart);
string lineStr=base.Lines[line]; int linestart=0; for(int i=0;i<line;i++) { linestart+=base.Lines[i].Length+1; } SendMessage(base.Handle, WM_SETREDRAW, 0, IntPtr.Zero);
base.SelectionStart=linestart; base.SelectionLength=lineStr.Length; base.SelectionColor=Color.Black; base.SelectionStart=selectStart; base.SelectionLength=0;
string[] words=lineStr.Split(new char[]{' '}); Parser parser=new Parser(this.language); for(int i=0;i<words.Length;i++) { if(parser.IsKeyWord(words[i])) { int length=0; for(int j=0;j<i;j++) { length+=words[j].Length; } length+=i;
int index=lineStr.IndexOf(words[i],length);
base.SelectionStart=linestart+index; base.SelectionLength=words[i].Length; base.SelectionColor=Color.Blue; base.SelectionStart=selectStart; base.SelectionLength=0; base.SelectionColor=Color.Black;
} } SendMessage(base.Handle, WM_SETREDRAW, 1, IntPtr.Zero); base.Refresh(); } base.OnTextChanged (e); }
public new bool WordWrap { get{return base.WordWrap;} }
public enum Languages { SQL, VBSCRIPT, CSHARP, JSHARP } private Languages language=Languages.SQL;
public Languages Language { get{return this.language;} set{this.language=value;} } } }

|