2012-06-22 52 views
6

我想在我的WPF应用程序中使用AvalonEdit作为XML文本编辑器。但是,当它遇到无效的语法时,它不会执行任何格式化(如波浪线)。用AvalonEdit显示无效的XML语法

我想知道是否可以使用AvalonEdit完成此功能,或者如果有其他替代方法。谢谢!

回答

15

我也希望利用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); 
    } 
}