「巨作!超长篇!纯GDI+实现」支持语法高亮和代码折叠的快速着色文本框控件

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


点击阅读原文


下载源代码 - 2.1 MB

下载DEMO - 159 KB

下载用于.NET Compact Framework 2.0的源代码和DEMO - 301.86 KB

下载帮助(CHM) - 1.7 MB


「巨作!超长篇!纯GDI+实现」支持语法高亮和代码折叠的快速着色文本框控件

© 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事件,还有可能用到TextChangingVisibleRangeChangedSelectionChanged事件。TextChanging事件在文本即将变化前发生。SelectionChanged事件在光标位置或是选中文本片段变化后发生。

代码折叠

我的组件允许通过使用CollapseBlock()方法实现折叠隐藏文本块的效果:

fastColoredTextBox1.CollapseBlock(fastColoredTextBox1.Selection.Start.iLine, 
                fastColoredTextBox1.Selection.End.iLine);

下图是该效果的演示:

「巨作!超长篇!纯GDI+实现」支持语法高亮和代码折叠的快速着色文本框控件

© 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");
}

下图是效果演示:

「巨作!超长篇!纯GDI+实现」支持语法高亮和代码折叠的快速着色文本框控件

折叠块可以互相嵌套。

要展开折叠后的代码块,只需要双击代码块或是折叠标记「+」。在折叠后的代码块上单击可以选中整块代码。你还可以用ExpandBlock()方法展开隐藏的代码块。

Demo程序包含了在文本中折叠所有#region..#endregion的演示。

折叠块还支持可视地定义光标所在处的代码块边界。这种情况下组件左侧绘制一条竖线(折叠指示器,即上图箭头范围内的垂直绿线——野比注)来指示光标所在的折叠区域。

延迟处理

很多事件(TextChangedSelectionChangedVisibleRangeChanged)都有其对应的延迟处理版本。延迟事件会在重要事件发生之后的某个时刻再触发。

这是什么意思呢?举个例子,如果用户快速输入文本,那么TextChanged事件在你输入每个字符的时候都会触发,而TextChangedDelayed只在你停止输入后才触发,而且只触发一次。

这对大量文本的延迟高亮着色来说是很有用的。

我的组件支持以下的延迟事件:TextChangedDelayedSelectionChangedDelayedVisibleRangeChangedDelayedDelayedEventsIntervalDelayedTextChangedInterval属性包含了延迟用到的时间。

导出为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

你可能感兴趣的:(文本框)