2012-06-22 5 views
6

Sto cercando di utilizzare AvalonEdit come editor di testo XML nella mia applicazione WPF. Tuttavia, non esegue alcuna formattazione (come le linee ondulate) quando incontra una sintassi non valida.Sintassi XML non valida con AvalonEdit

Mi piacerebbe sapere se tale funzione può essere eseguita utilizzando AvalonEdit o se esistono altre alternative. Grazie!

risposta

15

Stavo anche cercando di utilizzare l'evidenziazione della sintassi non valida xml. Osservando il codice sorgente di SharpDevelop ho notato che la segnalazione degli errori avveniva a un livello più alto rispetto al controllo AvalonEdit e non mi sembrava particolarmente adatta per il riutilizzo. Quindi ho provato a estrarre abbastanza codice per far partire un POC. Ecco cosa mi è venuto in mente ...

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