因为工作需要,用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
    // 
     = "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;}
   }
  }
 }
 
 
                     
            
        













 
                    

 
                 
                    