Last updated: 7 JUN, 2012
1st. initiates: 3 JUN, 2012
支持语法高亮和代码折叠的快速着色TextBox
[乌克兰]Pavel Torgashov著,野比译
带有语法高亮的自定义文本编辑器。
© Written by Pavel Torgashov 2011, translated by 野比「Conmajia」 2012
野比点评:语法高亮是几乎所有IDE都需要的功能。我们开发某些行业软件时常常也要用到这样的功能。Torgashov利用约1年时间,单枪匹马完成了这个FastColoredTextBox控件,功能丰富,效果华丽。和早期流行的ICSharpCode.TextEditor相比,有过之而无不及,非常强悍。是近年来极为难得的开源语法高亮控件。文中某些编程思想(如延迟事件处理、样式遮罩等)对于我们学习、提高自己的编程水平和设计思维,具有相当程度的指导意义。由于本控件功能极为强大,所以本文篇幅极长!翻译时间将不固定,请收藏或RSS。
下载用于.NET Compact Framework 2.0的源代码和DEMO - 301.86 KB
© Written by Pavel Torgashov 2011, translated by 野比「Conmajia」 2012
简介
在做某项目的过程中,我遇到了需要支持语法高亮的文本编辑器这样的情况。一开始,我用了一个从RichTextBox继承的组件。但是在我用它处理大量文本的时候,我发现用RichTextBox对大量文本片段着色(200或者更多),速度非常的慢。当必须用这样的着色方式进行动态处理,就会引起严重的问题。
所以我创建了我自己的文本组件——既没有用Windows自带的TextBox,也没有用RichTextBox。
文本的渲染完全通过纯GDI+方法完成。
这个组件能足够迅速地处理大量文本,而且包含各种工具,让我能轻松地完成动态语法高亮。
它可以任意设置选定文本符号的前景色、字体样式、背景色。你可以利用正则表达式轻松访问文本内容。同样,这个组件也支持自动换行、查找/替换、代码折叠和多步撤销/重复(Undo/Redo)。
© Written by Pavel Torgashov 2011, translated by 野比「Conmajia」 2012
实现
为了保存文本中每个字符,我使用了Char结构体:
public struct Char { public char c; public StyleIndex style; }
这个结构体保存了符号(char,2字节)和样式索引遮罩(StyleIndex,2字节)。所以,文本中每个字符消耗4字节的内存。利用List<Char>来实现符号的按行分组。
StyleIndex是用于字符的样式索引遮罩。StyleIndex的每一位(bit)都对应着绘制符号的样式。因为StyleIndex只有16位(2字节),所以控件只支持16种以内的不同样式。
样式被储存在单独的列表里:
public readonly Style[] Styles = new Style[sizeof(ushort)*8];
下面是利用样式渲染文本字符的典型实现:
public class TextStyle : Style { public Brush ForeBrush { get; set; } public Brush BackgroundBrush { get; set; } public FontStyle FontStyle { get; set; } public override void Draw(Graphics gr, Point position, Range range) { //绘制背景 if (BackgroundBrush != null) gr.FillRectangle(BackgroundBrush, position.X, position.Y, (range.End.iChar - range.Start.iChar) * range.tb.CharWidth, range.tb.CharHeight); //绘制字符 Font f = new Font(range.tb.Font, FontStyle); Line line = range.tb[range.Start.iLine]; float dx = range.tb.CharWidth; float y = position.Y - 2f; float x = position.X - 2f; Brush foreBrush = this.ForeBrush ?? new SolidBrush(range.tb.ForeColor); for (int i = range.Start.iChar; i < range.End.iChar; i++) { //绘制单个字符 gr.DrawString(line[i].c.ToString(), f, foreBrush, x, y); x += dx; } } }
你可以从Style类继承创建自定义样式。
Range类表示了一段连续的文本块,利用给定的初始和结束位置,处理文本片段:
public class Range { Place start; Place end; } public struct Place { int iLine; int iChar; }
© Written by Pavel Torgashov 2011, translated by 野比「Conmajia」 2012
© Written by Pavel Torgashov 2011, translated by 野比「Conmajia」 2012
使用源代码
语法高亮
和RichTextBox不同,我的组件不是用RTF来实现的语法高亮。组件保存了每个符号的颜色和样式信息,这就意味着每次输入文本的时候都要重新进行着色。所以就有了TextChanged事件。
通过给TextChanged事件传递一个Range对象作为参数,可以允许组件每次只对变动了的那一部分文本进行高亮着色。
在指定搜索模式(正则表达式)后,利用重载的Range.SetStyle()就能找出需要着色的文本片段。例如,下面这样的代码可以用来搜索C#里的注释(以「//」开头的一行文本):
Style GreenStyle = new TextStyle(Brushes.Green, null, FontStyle.Italic); ... private void fastColoredTextBox1_TextChanged(object sender, TextChangedEventArgs e) { //清除样式 e.ChangedRange.ClearStyle(GreenStyle); //高亮注释 e.ChangedRange.SetStyle(GreenStyle, @"//.*$", RegexOptions.Multiline); }
尽管使用SetStyle()方法可以对符合给定正则表达式的文本片段进行着色,但如果该正则表达式包含名为「range」的组的话,就会对所有名为「range」的组进行着色。下面的代码演示了怎样将关键字「class」、「struct」和「enum」后面的单词加粗:
e.ChangedRange.SetStyle(BoldStyle, @"\b(class|struct|enum)\s+(?<range>[\w_]+?)\b");
除了TextChanged事件,还有可能用到TextChanging、VisibleRangeChanged和SelectionChanged事件。TextChanging事件在文本即将变化前发生。SelectionChanged事件在光标位置或是选中文本片段变化后发生。
代码折叠
我的组件允许通过使用CollapseBlock()方法实现折叠隐藏文本块的效果:
fastColoredTextBox1.CollapseBlock(fastColoredTextBox1.Selection.Start.iLine, fastColoredTextBox1.Selection.End.iLine);
© Written by Pavel Torgashov 2011, translated by 野比「Conmajia」 2012
我的组件还支持自动搜索折叠片段(折叠区域)。可以在TextChanged事件里设置Range.SetFoldingMarkers()的模式(正则表达式)来查找折叠块的起始和结束位置。
例如,要搜索「{..}」以及「#region..#endregion」包围的代码块,就这样设置:
private void fastColoredTextBox1_TextChanged(object sender, TextChangedEventArgs e) { //清除变化部分的折叠标记 e.ChangedRange.ClearFoldingMarkers(); //设置折叠标记 e.ChangedRange.SetFoldingMarkers("{", "}"); e.ChangedRange.SetFoldingMarkers(@"#region\b", @"#endregion\b"); }
折叠块可以互相嵌套。
要展开折叠后的代码块,只需要双击代码块或是折叠标记「+」。在折叠后的代码块上单击可以选中整块代码。你还可以用ExpandBlock()方法展开隐藏的代码块。
Demo程序包含了在文本中折叠所有#region..#endregion的演示。
折叠块还支持可视地定义光标所在处的代码块边界。这种情况下组件左侧绘制一条竖线(折叠指示器,即上图箭头范围内的垂直绿线——野比注)来指示光标所在的折叠区域。
延迟处理
很多事件(TextChanged、SelectionChanged、VisibleRangeChanged)都有其对应的延迟处理版本。延迟事件会在重要事件发生之后的某个时刻再触发。
这是什么意思呢?举个例子,如果用户快速输入文本,那么TextChanged事件在你输入每个字符的时候都会触发,而TextChangedDelayed只在你停止输入后才触发,而且只触发一次。
这对大量文本的延迟高亮着色来说是很有用的。
我的组件支持以下的延迟事件:TextChangedDelayed、SelectionChangedDelayed、VisibleRangeChangedDelayed。DelayedEventsInterval和DelayedTextChangedInterval属性包含了延迟用到的时间。
导出为HTML
本组件有一个Html属性,这个属性会返回着色文本的HTML代码。你还可以用ExportToHTML类来导出HTML,以便打印或是贴到网站上。
剪贴板
本组件可以用两种格式复制文本——普通文本和HTML。
如果目标程序支持插入HTML(如Microsoft Word),那么就会粘贴着色后的文本。其他情况下(如记事本)会粘贴普通文本。
快捷键
本控件支持以下快捷键:
© Written by Pavel Torgashov 2011, translated by 野比「Conmajia」 2012
括号高亮
(未完待续)
© Written by Pavel Torgashov 2011, translated by 野比「Conmajia」 2012