【支持NET6】
github地址:GitHub - icsharpcode/AvalonEdit: The WPF-based text editor component used in SharpDevelop
官方文档:AvalonEdit - Table of Content
它有个很好的项目GitHub - icsharpcode/WpfDesigner: The WPF Designer from SharpDevelop
GitHub - WPFDevelopersOrg/XamlViewer: XAML Viewer is a lightweight XAML editor.
新建wpf .net framework4.5的项目,nuget搜avalonedit
当前版本6.1.3.50
准备工作
<Window x:Class="avaedit.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:avaedit" mc:Ignorable="d" WindowStartupLocation="CenterScreen" xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit" Title="" Height="700" Width="1200"> <Grid> </Grid> </Window>
入门
<Window x:Class="avaedit.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:avaedit" mc:Ignorable="d" WindowStartupLocation="CenterScreen" xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit" Title="" Height="700" Width="1200"> <Grid> <avalonEdit:TextEditor x:Name="textEditor" SyntaxHighlighting="C#"/> </Grid> </Window>
运行后,粘贴一段C#代码
看了下代码,原生自己写的
显示行号
折叠功能
这里以xml文件做demo,内置了xml的高亮规则和折叠
<?xml version="1.0"?> <SyntaxDefinition name="Custom Highlighting" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008"> <Color name="Comment" foreground="Green" /> <Color name="String" foreground="Blue" /> <!-- This is the main ruleset. --> <RuleSet> <Span color="Comment" begin="//" /> <Span color="Comment" multiline="true" begin="/\*" end="\*/" /> <Span color="String"> <Begin>"</Begin> <End>"</End> <RuleSet> <!-- nested span for escape sequences --> <Span begin="\\" end="." /> </RuleSet> </Span> <Keywords fontWeight="bold" foreground="Blue"> <Word>if</Word> <Word>else</Word> <!-- ... --> </Keywords> <Keywords fontWeight="bold" fontStyle="italic" foreground="Red"> <Word>AvalonEdit</Word> </Keywords> <!-- Digits --> <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>
后台单击折叠按钮
private static FoldingManager foldingManager = null; private static XmlFoldingStrategy foldingStrategy = new XmlFoldingStrategy(); private void zhedie_Click(object sender, RoutedEventArgs e) { //默认语法高亮规则 textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(".xml"); //折叠 foldingManager = FoldingManager.Install(textEditor.TextArea); //textEditor.Text = ""; foldingStrategy.UpdateFoldings(foldingManager, textEditor.Document); }
运行后,粘贴一段xml
单击折叠后
折叠后,复制文本都是可以的
行号的显示颜色 LineNumbersForeground
引入xmlns:system="clr-namespace:System;assembly=mscorlib",xaml上设置数字
<avalonEdit:TextEditor LineNumbersForeground="Red" Grid.Row="1" x:Name="textEditor" SyntaxHighlighting="C#" ShowLineNumbers="True"> <avalonEdit:TextEditor.Options> <avalonEdit:TextEditorOptions ShowSpaces="True" > </avalonEdit:TextEditorOptions> </avalonEdit:TextEditor.Options> </avalonEdit:TextEditor>
通过Options指定设置,F12可以看到很多设置,比如ShowSpaces="True",显示虚线
删掉了一些xml节点,折叠节点不是实时更新 折叠的,修改折叠按钮后面的代码
private void zhedie_Click(object sender, RoutedEventArgs e) { //默认语法高亮规则 textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(".xml"); //折叠 if (foldingManager != null) { FoldingManager.Uninstall(foldingManager); foldingManager.Clear(); } foldingManager = FoldingManager.Install(textEditor.TextArea); //textEditor.Text = ""; foldingStrategy.UpdateFoldings(foldingManager, textEditor.Document); }
下面几个设置没看到效果
<avalonEdit:TextEditorOptions ShowSpaces="True" WordWrapIndentation="14" InheritWordWrapIndentation="true"> <avalonEdit:TextEditorOptions.ColumnRulerPosition> <system:Int32>10</system:Int32> </avalonEdit:TextEditorOptions.ColumnRulerPosition> </avalonEdit:TextEditorOptions>
输入点号,自动补全
需要先自己继承 ICompletionData 接口创建提示数据
public class CompletionData : ICompletionData { public CompletionData(string text) { Text = text; } public ImageSource Image => null; public string Text { get; } public object Content => Text; public object Description =>this.Text +" 的描述"; public double Priority { get; } public void Complete(TextArea textArea, ISegment completionSegment, EventArgs insertionRequestEventArgs) { textArea.Document.Replace(completionSegment, Text); } }
输入点号,主要CompletionWindow这个窗体show就可以了,主要是自己要判断什么时候弹,弹什么提示
private CompletionWindow _completionWindow; private void TextAreaOnTextEntered(object sender, TextCompositionEventArgs e) { if (e.Text == ".") { _completionWindow = new CompletionWindow(textEditor.TextArea); var completionData = _completionWindow.CompletionList.CompletionData; completionData.Add(new CompletionData("Name")); completionData.Add(new CompletionData("Gender")); completionData.Add(new CompletionData("Class")); _completionWindow.Show(); _completionWindow.Closed += (o, args) => _completionWindow = null; } }
输入.
暂时不知道怎么拿到前面的 Student这个单词的值,毕竟要根据什么值,然后提示啥
=============2022 02 14 ==============================
默认文本区域没有右键菜单的
验证xml,把当前的xml内容用XmlDocument. Load 加载文本,报的异常来验证xml
https://github.com/ay2015/avaedit/
效果图
下面2个接口,对文字背景修改,一个应该是线
IBackgroundRenderer, IVisualLineTransformer
定义一个外观的文本块,继承TextSegment
public sealed class TextMarker : TextSegment { public TextMarker(int startOffset, int length) { StartOffset = startOffset; Length = length; } public Color? BackgroundColor { get; set; } public Color MarkerColor { get; set; } public string ToolTip { get; set; } }
public sealed class TextMarker : TextSegment { public TextMarker(int startOffset, int length) { StartOffset = startOffset; Length = length; } public Color? BackgroundColor { get; set; } public Color MarkerColor { get; set; } public string ToolTip { get; set; } } public class TextMarkerService : IBackgroundRenderer, IVisualLineTransformer { private readonly TextEditor textEditor; private readonly TextSegmentCollection<TextMarker> markers; public TextMarkerService(TextEditor textEditor) { this.textEditor = textEditor; markers = new TextSegmentCollection<TextMarker>(textEditor.Document); } public void Draw(TextView textView, DrawingContext drawingContext) { if (markers == null || !textView.VisualLinesValid) { return; } var visualLines = textView.VisualLines; if (visualLines.Count == 0) { return; } int viewStart = visualLines.First().FirstDocumentLine.Offset; int viewEnd = visualLines.Last().LastDocumentLine.EndOffset; foreach (TextMarker marker in markers.FindOverlappingSegments(viewStart, viewEnd - viewStart)) { if (marker.BackgroundColor != null) { var geoBuilder = new BackgroundGeometryBuilder { AlignToWholePixels = true, CornerRadius = 3 }; geoBuilder.AddSegment(textView, marker); Geometry geometry = geoBuilder.CreateGeometry(); if (geometry != null) { Color color = marker.BackgroundColor.Value; var brush = new SolidColorBrush(color); brush.Freeze(); drawingContext.DrawGeometry(brush, null, geometry); } } foreach (Rect r in BackgroundGeometryBuilder.GetRectsForSegment(textView, marker)) { Point startPoint = r.BottomLeft; Point endPoint = r.BottomRight; var usedPen = new Pen(new SolidColorBrush(marker.MarkerColor), 1); usedPen.Freeze(); const double offset = 2.5; int count = Math.Max((int)((endPoint.X - startPoint.X) / offset) + 1, 4); var geometry = new StreamGeometry(); using (StreamGeometryContext ctx = geometry.Open()) { ctx.BeginFigure(startPoint, false, false); ctx.PolyLineTo(CreatePoints(startPoint, endPoint, offset, count).ToArray(), true, false); } geometry.Freeze(); drawingContext.DrawGeometry(Brushes.Transparent, usedPen, geometry); break; } } } public KnownLayer Layer { get { return KnownLayer.Selection; } } public void Transform(ITextRunConstructionContext context, IList<VisualLineElement> elements) { } private IEnumerable<Point> CreatePoints(Point start, Point end, double offset, int count) { for (int i = 0; i < count; i++) { yield return new Point(start.X + (i * offset), start.Y - ((i + 1) % 2 == 0 ? offset : 0)); } } public void Clear() { foreach (TextMarker m in markers) { Remove(m); } } private void Remove(TextMarker marker) { if (markers.Remove(marker)) { Redraw(marker); } } private void Redraw(ISegment segment) { textEditor.TextArea.TextView.Redraw(segment); } public void Create(int offset, int length, string message) { var m = new TextMarker(offset, length); markers.Add(m); m.MarkerColor = Colors.Red; m.ToolTip = message; Redraw(m); } public IEnumerable<TextMarker> GetMarkersAtOffset(int offset) { return markers == null ? Enumerable.Empty<TextMarker>() : markers.FindSegmentsContaining(offset); } }
新建一个用户控件AvaEditor.xaml
前台代码
<UserControl x:Class="avaedit.AvaEditor" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:avaedit" mc:Ignorable="d" xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit" d:DesignHeight="450" d:DesignWidth="800"> <UserControl.CommandBindings> <CommandBinding Command="local:AvaEditor.ValidateCommand" Executed="Validate"/> </UserControl.CommandBindings> <Grid> <avalonEdit:TextEditor x:Name="textEditor" SyntaxHighlighting="XML" ShowLineNumbers="True" > <avalonEdit:TextEditor.ContextMenu> <ContextMenu> <MenuItem Command="Undo" /> <MenuItem Command="Redo" /> <Separator/> <MenuItem Command="Cut" /> <MenuItem Command="Copy" /> <MenuItem Command="Paste" /> <Separator/> <MenuItem Command="local:AvaEditor.ValidateCommand" /> </ContextMenu> </avalonEdit:TextEditor.ContextMenu> </avalonEdit:TextEditor> </Grid> </UserControl>
后台代码:
public partial class AvaEditor : UserControl { private static readonly ICommand validateCommand = new RoutedUICommand("验证XML", "Validate", typeof(AvaEditor), new InputGestureCollection { new KeyGesture(Key.V, ModifierKeys.Control | ModifierKeys.Shift) }); public static ICommand ValidateCommand { get { return validateCommand; } } public AvaEditor() { InitializeComponent(); Loaded += AvaEditor_Loaded; } private ToolTip toolTip; private TextMarkerService textMarkerService; private void AvaEditor_Loaded(object sender, RoutedEventArgs e) { Loaded -= AvaEditor_Loaded; textMarkerService = new TextMarkerService(textEditor); var textView = textEditor.TextArea.TextView; textView.BackgroundRenderers.Add(textMarkerService); textView.LineTransformers.Add(textMarkerService); textView.Services.AddService(typeof(TextMarkerService), textMarkerService); textView.MouseHover += MouseHover; textView.MouseHoverStopped += TextEditorMouseHoverStopped; textView.VisualLinesChanged += VisualLinesChanged; } private void MouseHover(object sender, MouseEventArgs e) { var pos = textEditor.TextArea.TextView.GetPositionFloor(e.GetPosition(textEditor.TextArea.TextView) + textEditor.TextArea.TextView.ScrollOffset); bool inDocument = pos.HasValue; if (inDocument) { TextLocation logicalPosition = pos.Value.Location; int offset = textEditor.Document.GetOffset(logicalPosition); var markersAtOffset = textMarkerService.GetMarkersAtOffset(offset); TextMarker markerWithToolTip = markersAtOffset.FirstOrDefault(marker => marker.ToolTip != null); if (markerWithToolTip != null) { if (toolTip == null) { toolTip = new ToolTip(); toolTip.Closed += ToolTipClosed; toolTip.PlacementTarget = this; toolTip.Content = new TextBlock { Text = markerWithToolTip.ToolTip, TextWrapping = TextWrapping.Wrap }; toolTip.IsOpen = true; e.Handled = true; } } } } void ToolTipClosed(object sender, RoutedEventArgs e) { toolTip = null; } void TextEditorMouseHoverStopped(object sender, MouseEventArgs e) { if (toolTip != null) { toolTip.IsOpen = false; e.Handled = true; } } private void VisualLinesChanged(object sender, EventArgs e) { if (toolTip != null) { toolTip.IsOpen = false; } } private void Validate(object sender, ExecutedRoutedEventArgs e) { IServiceProvider sp = textEditor; var markerService = (TextMarkerService)sp.GetService(typeof(TextMarkerService)); markerService.Clear(); try { var document = new XmlDocument { XmlResolver = null }; document.LoadXml(textEditor.Document.Text); } catch (XmlException ex) { DisplayValidationError(ex.Message, ex.LinePosition, ex.LineNumber); } } private void DisplayValidationError(string message, int linePosition, int lineNumber) { if (lineNumber >= 1 && lineNumber <= textEditor.Document.LineCount) { int offset = textEditor.Document.GetOffset(new TextLocation(lineNumber, linePosition)); int endOffset = TextUtilities.GetNextCaretPosition(textEditor.Document, offset, System.Windows.Documents.LogicalDirection.Forward, CaretPositioningMode.WordBorderOrSymbol); if (endOffset < 0) { endOffset = textEditor.Document.TextLength; } int length = endOffset - offset; if (length < 2) { length = Math.Min(2, textEditor.Document.TextLength - offset); } textMarkerService.Create(offset, length, message); } } }
然后找一个页面使用下就行了
<local:AvaEditor Grid.Row="1"/>
加个背景色
public void Create(int offset, int length, string message) { var m = new TextMarker(offset, length); markers.Add(m); m.MarkerColor = Colors.Red; m.BackgroundColor = Colors.Yellow; m.ToolTip = message; Redraw(m); }
自定义高亮规则
官方示例
<SyntaxDefinition name="Custom Highlighting" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008"> <Color name="Comment" foreground="#FF00FF" /> <Color name="String" foreground="Blue" /> <!-- This is the main ruleset. --> <RuleSet> <Span color="Comment" begin="//" /> <Span color="Comment" multiline="true" begin="/\*" end="\*/" /> <Span color="String"> <Begin>"</Begin> <End>"</End> <RuleSet> <!-- nested span for escape sequences --> <Span begin="\\" end="." /> </RuleSet> </Span> <Keywords fontWeight="bold" foreground="Blue"> <Word>if</Word> <Word>else</Word> <!-- ... --> </Keywords> <Keywords fontWeight="bold" fontStyle="italic" foreground="Red"> <Word>AvalonEdit</Word> </Keywords> <!-- Digits --> <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>
给颜色取名字,下方定义规则
下面定义个lua的高亮规则
新建个txt,改名字lua.xshd 文件类型改为 嵌入的资源,你也可以是资源,主要嵌入到程序还能读取xml内容
<?xml version="1.0"?> <SyntaxDefinition name="SharpLua" extensions=".slua;.lua" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008"> <!-- The named colors 'Comment' and 'String' are used in SharpDevelop to detect if a line is inside a multiline string/comment --> <Color name="Comment" foreground="#ff999999" exampleText="-- comment" /> <Color name="String" foreground="#fff99157" /> <Color name="Punctuation" /> <Color name="MethodCall" foreground="#ffffcc66" fontWeight="bold"/> <Color name="NumberLiteral" foreground="#ff99cc99"/> <Color name="NilKeyword" fontWeight="bold"/> <Color name="Keywords" fontWeight="bold" foreground="#ff6699cc" /> <Color name="GotoKeywords" foreground="#ffcc99cc" /> <Color name="Visibility" fontWeight="bold" foreground="#fff99157"/> <Color name="TrueFalse" fontWeight="bold" foreground="#ff66cccc" /> <RuleSet name="CommentMarkerSet"> <Keywords fontWeight="bold" foreground="#fff2777a"> <Word>TODO</Word> <Word>FIXME</Word> </Keywords> <Keywords fontWeight="bold" foreground="#fff2777a"> <Word>HACK</Word> <Word>UNDONE</Word> </Keywords> </RuleSet> <!-- This is the main ruleset. --> <RuleSet> <Span color="Comment"> <Begin color="XmlDoc/DocComment">---</Begin> <RuleSet> <Import ruleSet="XmlDoc/DocCommentSet"/> <Import ruleSet="CommentMarkerSet"/> </RuleSet> </Span> <Span color="Comment" ruleSet="CommentMarkerSet" multiline="true"> <Begin>--\[[=]*\[</Begin> <End>\][=]*]</End> </Span> <Span color="Comment" ruleSet="CommentMarkerSet"> <Begin>--</Begin> </Span> <Span color="String"> <Begin>"</Begin> <End>"</End> <RuleSet> <!-- span for escape sequences --> <Span begin="\\" end="."/> </RuleSet> </Span> <Span color="String"> <Begin>'</Begin> <End>'</End> <RuleSet> <!-- span for escape sequences --> <Span begin="\\" end="."/> </RuleSet> </Span> <Span color="String" multiline="true"> <Begin color="String">\[[=]*\[</Begin> <End>\][=]*]</End> </Span> <Keywords color="TrueFalse"> <Word>true</Word> <Word>false</Word> </Keywords> <Keywords color="Keywords"> <Word>and</Word> <Word>break</Word> <Word>do</Word> <Word>else</Word> <Word>elseif</Word> <Word>end</Word> <Word>false</Word> <Word>for</Word> <Word>function</Word> <Word>if</Word> <Word>in</Word> <Word>local</Word> <!--<Word>nil</Word>--> <Word>not</Word> <Word>or</Word> <Word>repeat</Word> <Word>return</Word> <Word>then</Word> <Word>true</Word> <Word>until</Word> <Word>while</Word> <Word>using</Word> <Word>continue</Word> </Keywords> <Keywords color="GotoKeywords"> <Word>break</Word> <Word>return</Word> </Keywords> <Keywords color="Visibility"> <Word>local</Word> </Keywords> <Keywords color="NilKeyword"> <Word>nil</Word> </Keywords> <!-- Mark previous rule--> <Rule color="MethodCall"> \b [\d\w_]+ # an identifier (?=\s*\() # followed by ( </Rule> <Rule color="MethodCall"> \b [\d\w_]+ # an identifier (?=\s*\") # followed by " </Rule> <Rule color="MethodCall"> \b [\d\w_]+ # an identifier (?=\s*\') # followed by ' </Rule> <Rule color="MethodCall"> \b [\d\w_]+ # an identifier (?=\s*\{) # followed by { </Rule> <Rule color="MethodCall"> \b [\d\w_]+ # an identifier (?=\s*\[) # followed by [ </Rule> <!-- Digits --> <Rule color="NumberLiteral"> \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> <Rule color="Punctuation"> [?,.;()\[\]{}+\-/%*<>^+~!|&]+ </Rule> </RuleSet> </SyntaxDefinition>
然后在后台找个loaded的地方指定SyntaxHighlighting属性
string name = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name + ".lua.xshd"; System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly(); using (System.IO.Stream s = assembly.GetManifestResourceStream(name)) { using (XmlTextReader reader = new XmlTextReader(s)) { var xshd = HighlightingLoader.LoadXshd(reader); textEditor.SyntaxHighlighting = HighlightingLoader.Load(xshd, HighlightingManager.Instance); } }
运行后,粘贴lua代码
-- 以只读方式打开文件 file = io.open("test.lua", "r") -- 设置默认输入文件为 test.lua io.input(file) -- 输出文件第一行 print(io.read()) -- 关闭打开的文件 io.close(file) -- 以附加的方式打开只写文件 file = io.open("test.lua", "a") -- 设置默认输出文件为 test.lua io.output(file) -- 在文件最后一行添加 Lua 注释 io.write("-- test.lua 文件末尾注释") -- 关闭打开的文件 io.close(file)
你也可以设置文件为资源
using (XmlTextReader xmlTextReader = new XmlTextReader(Application.GetResourceStream(new Uri("pack://application:,,,/avaedit;component/lua.xshd")).Stream))
{
textEditor.SyntaxHighlighting = HighlightingLoader.Load(xmlTextReader, HighlightingManager.Instance);
}
加载文件textEditor.Load(filename);
官方写法,可以给个名字,注册文件后缀和 高亮规则,那么就可以直接指定这个名字了,而不是传递一些 高亮规则的xml
IHighlightingDefinition customHighlighting; using (Stream s = typeof(Window1).Assembly.GetManifestResourceStream("AvalonEdit.Sample.CustomHighlighting.xshd")) { if (s == null) throw new InvalidOperationException("Could not find embedded resource"); using (XmlReader reader = new XmlTextReader(s)) { customHighlighting = ICSharpCode.AvalonEdit.Highlighting.Xshd. HighlightingLoader.Load(reader, HighlightingManager.Instance); } } // and register it in the HighlightingManager HighlightingManager.Instance.RegisterHighlighting("Custom Highlighting", new string[] { ".cool" }, customHighlighting);
增加搜索面板
SearchPanel.Install(textEditor.TextArea);
运行后按 Ctrl+F ,面板有点丑,自己可以修改源码改外观
=============2022 02 16 ==============================