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];
事实上, Style就是字符、背景、边框和其他文本设计元素的渲染器。
下面是利用样式渲染文本字符的典型实现:
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;
}
}
}
TextStyle包含了文本的前景色、背景色和字体样式。在创建新样式时,组件会检查它的样式表,如果样式不存在,就用它的索引新建一个。
你可以从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);
}
上面的代码在着色前,先调用了 Range.ClearStyle()方法清除了之前的文本样式。
尽管使用SetStyle()方法可以对符合给定正则表达式的文本片段进行着色,但如果该正则表达式包含名为「range」的组的话,就会对所有名为「range」的组进行着色。下面的代码演示了怎样将关键字「class」、「struct」和「enum」后面的单词加粗:
e.ChangedRange.SetStyle(BoldStyle, @"\b(class|struct|enum)\s+(?<range>[\w_]+?)\b");
本文给出的示例程序(Demo)中实现了用于C#、VB、HTML和其他部分语言语法高亮的 TextChanged事件代码。
除了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),那么就会粘贴着色后的文本。其他情况下(如记事本)会粘贴普通文本。
快捷键
本控件支持以下快捷键:
- 左、右、上、下、Home、End、PageUp、PageDown - 移动光标
- Shift+(左、右、上、下、Home、End、PageUp、PageDown) - 带选区移动光标
- Ctrl+F、Ctrl+H - 显示查找和替换对话框
- F3 - 查找下一个
- Ctrl+G - 显示「转到」对话框
- Ctrl+(C、V、X) - 标准剪贴板操作
- Ctrl+A - 全选文本
- Ctrl+Z、Alt+退格键、Ctrl+R - 撤销/重做
- Tab、Shift+Tab - 增加/减少缩进量
- Ctrl+Home、Ctrl+End - 转到文本最前/后面
- Shift+Ctrl+Home、Shift+Ctrl+右 - 向左/右移动一个单词
- Ctrl+-、Shift+Ctrl+- - 向后/前移动
- Ctrl+U、Shift+Ctrl+U - 将选中文本转换成大写/小写
- Ctrl+Shift+C -注释/取消注释
- Ins - 插入/替换
- Ctrl+退格键、Ctrl+Del - 删除左边/右边的单词
- Alt+鼠标、Alt+Shift+(上、下、左、右) - 列选择模式
© Written by Pavel Torgashov 2011, translated by 野比「Conmajia」 2012
括号高亮
(未完待续)
© Written by Pavel Torgashov 2011, translated by 野比「Conmajia」 2012