Отображение недопустимого синтаксиса XML с помощью AvalonEdit
Я пытаюсь использовать AvalonEdit в качестве текстового редактора XML в моем приложении WPF. Однако он не выполняет никакого форматирования (например, волнистые линии), когда он сталкивается с недопустимым синтаксисом.
Я хотел бы знать, может ли такая функция быть выполнена с помощью AvalonEdit, или есть другие альтернативы. Спасибо!
1 ответ:
Я также хотел использовать подсветку недопустимого синтаксиса xml. Глядя на исходный код SharpDevelop, я заметил, что отчеты об ошибках были сделаны на более высоком уровне, чем элемент управления AvalonEdit, и не казались особенно подходящими для повторного использования. Поэтому я попытался извлечь достаточно кода, чтобы запустить POC. Вот что я придумал...
<UserControl x:Class="WpfTestApp.Xml.XmlEditor" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit" xmlns:WpfTestApp="clr-namespace:WpfTestApp.Xml"> <UserControl.CommandBindings> <CommandBinding Command="WpfTestApp:XmlEditor.ValidateCommand" Executed="Validate"/> </UserControl.CommandBindings> <avalonedit:TextEditor Name="textEditor" FontFamily="Consolas" SyntaxHighlighting="XML" FontSize="8pt"> <avalonedit:TextEditor.Options> <avalonedit:TextEditorOptions ShowSpaces="True" ShowTabs="True"/> </avalonedit:TextEditor.Options> <avalonedit:TextEditor.ContextMenu> <ContextMenu> <MenuItem Command="Undo" /> <MenuItem Command="Redo" /> <Separator/> <MenuItem Command="Cut" /> <MenuItem Command="Copy" /> <MenuItem Command="Paste" /> <Separator/> <MenuItem Command="WpfTestApp:XmlEditor.ValidateCommand" /> </ContextMenu> </avalonedit:TextEditor.ContextMenu> </avalonedit:TextEditor> </UserControl>
.
public partial class XmlEditor : UserControl { private static readonly ICommand validateCommand = new RoutedUICommand("Validate XML", "Validate", typeof(MainWindow), new InputGestureCollection { new KeyGesture(Key.V, ModifierKeys.Control | ModifierKeys.Shift) }); private readonly TextMarkerService textMarkerService; private ToolTip toolTip; public static ICommand ValidateCommand { get { return validateCommand; } } public XmlEditor() { InitializeComponent(); textMarkerService = new TextMarkerService(textEditor); TextView 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); TextMarkerService.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); } } }
.
public class TextMarkerService : IBackgroundRenderer, IVisualLineTransformer { private readonly TextEditor textEditor; private readonly TextSegmentCollection<TextMarker> markers; 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 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); } }