很多工程软件拥有自己定义的脚本语言,作为程序员用惯了具有高亮显示和智能提示功能的编辑器,所以针对特定的脚本自己开发一个编辑器。主要采用WPF、C#语言以及AvalonEdit控件。
AvlonEdit控件
AvalonEdit是基于WPF的代码显示控件,可以支持代码高亮显示、智能提示、代码折叠等功能。

AvalonEdit项目官网
在WPF中使用AvalonEdit非常简单,直接Nuget安装,然后引入命名空间xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
,最后直接使用即可<avalonEdit:TextEditor/>
。因为本文后面要实现自定义替换,需要对源码进行修改及重新编译,所以最好直接下载源码。
实现自定义高亮显示
AvalonEdit已经内置了C#、C++、Java等常见语言的高亮显示,如果要为自定义的语言进行语法高亮需要写一个*.xshd文件,该文件的基本使用如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <?xml version="1.0"?> <SyntaxDefinition name="Custom Highlighting" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008"> <Color name="Comment" foreground="#C6B1B1" exampleText="* comment"/> <Color name="Card" fontWeight="bold" foreground="#960092" exampleText="=CSTR"/> <Color name="Field" fontWeight="bold" foreground="#3A76D7" exampleText="CA"/> <RuleSet> <Span color="Comment" begin="//" /> <Span color="Comment" multiline="true" begin="/\*" end="\*/" /> <Span color="String"> <Begin>"</Begin> <End>"</End> <RuleSet> <Span begin="\\" end="." /> </RuleSet> </Span> <Keywords fontWeight="bold" foreground="Blue"> <Word>if</Word> <Word>else</Word> </Keywords> <Rule foreground="DarkBlue"> \b0[xX][0-9a-fA-F]+ # hex number | \b ( \d+(\.[0-9]+)? #number with optional floating point | \.[0-9]+ #or just starting with floating point ) ([eE][+-]?[0-9]+)? # optional exponent </Rule> </RuleSet> </SyntaxDefinition>
|
自定义完*.xshd文件后,一定要设置文件的属性

设置完成后,需要在程序中设置加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| IHighlightingDefinition customHighlighting; using (Stream s = typeof(MainWindow).Assembly.GetManifestResourceStream("NotConvertPeps.PEPSHighlighting.xshd")) { using (XmlReader reader = new XmlTextReader(s)) { customHighlighting = HighlightingLoader.Load(reader, HighlightingManager.Instance); } }
HighlightingManager.Instance.RegisterHighlighting("Custom Highlighting", new string[] { ".fre" }, customHighlighting);
InitializeComponent();
txtEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(".fre");
|
设置完成后,看下效果

实现文本搜索
AvalonEdit已经具有了搜索功能,新版本只需使用ICSharpCode.AvalonEdit.Search.SearchPanel.Install(txtEditor);
便可以使用Ctrl+F调出搜索栏,该搜索栏具有是否忽略大小写、是否全字匹配、是否使用正则三个设置项,而且还有背景显示、下拉框自动下拉等功能,基本满足要求。

实现文本替换
很遗憾AvalonEdit没有提供像搜索栏一样的功能,必须自己来实现。
自定义搜索栏用户控件
仿照VS的替换栏进行页面设计

其中注册replaceContent、findContent、CareCase、MatchAll等依赖属性进行绑定,值得注意的是,用户控件在绑定的时候,source要用RelativeSource,否则不能实现数据更新,正确绑定方式如下:
1 2 3 4 5
| <CheckBox x:Name="chxCareCase" IsChecked="{Binding CareCase,RelativeSource={RelativeSource AncestorType=local:ReplaceControl}}" Template="{StaticResource ToggleButtonControlTemplate2}" ToolTip="区分大小写" />
|
实现自定义搜索
因为替换操作仍然需要先查询再替换,所以需要自定义实现搜索。实现思路可以是得到textEditor中的text,然后使用string.index等方法进行,但是这样太麻烦,而且还需要自定义搜索结果的背景高亮以及文本选择。所以要换个思路,因为AvalonEdit已经提供了搜索功能,所以一定会有相关的接口,与查询有关的代码都存在于ICSharpCode.AvalonEdit.Search
命名空间下,主要的查询方法存在于ICSharpCode.AvalonEdit.Search.SearchPanel
中,其中最关键的查询方法是SearchStrategyFactory.Create(SearchPattern, !MatchCase, WholeWords, UseRegex ? SearchMode.RegEx : SearchMode.Normal);
除了该方法外,我们还需要进行上一个和下一个搜索以及搜索结果的背景高亮显示,这都和SearchResultBackgroundRenderer
类相关,但是AvalonEdit官方源码中,该类的访问权限是Private
,所以需要将访问权限改为public
,然后重新编译。
实现自定义搜索功能C#代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| SearchResultBackgroundRenderer renderer = new SearchResultBackgroundRenderer(); void DoFind() { renderer.CurrentResults.Clear(); if (!string.IsNullOrEmpty(replaceUserControl.findContent)) { textArea.TextView.BackgroundRenderers.Clear(); textArea.TextView.BackgroundRenderers.Add(renderer); ISearchStrategy strategy = SearchStrategyFactory.Create(replaceUserControl.findContent, !replaceUserControl.CareCase, replaceUserControl.MatchAll, replaceUserControl.regex ? SearchMode.RegEx : SearchMode.Normal); var results = strategy.FindAll(textArea.Document, 0, textArea.Document.TextLength); foreach (SearchResult result in results) { renderer.CurrentResults.Add(result); } } }
|
实现下一个
1 2 3 4 5 6 7 8 9 10 11 12
| private void FindNext(object sender, RoutedEventArgs e) { DoFind(); SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset + 1); if (result == null) result = renderer.CurrentResults.FirstSegment; if (result != null) { SelectResult(result); } }
|
实现上一个
1 2 3 4 5 6 7 8 9 10 11 12 13
| private void FindPre(object sender, RoutedEventArgs e) { DoFind(); SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset); if (result != null) result = renderer.CurrentResults.GetPreviousSegment(result); if (result == null) result = renderer.CurrentResults.LastSegment; if (result != null) { SelectResult(result); } }
|
实现效果

实现自定义替换
AvalonEdit提供了Document.Replace方法,可以直接使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| private void ReplaceNext(object sender, RoutedEventArgs e) { string replace = replaceUserControl.replaceContent; DoFind(); SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset + 1); if (result == null) result = renderer.CurrentResults.FirstSegment; if (result != null) { SelectResult(result); this.txtEditor.Document.Replace(result.StartOffset, result.Length, replace); DoFind(); } }
|
