AvalonEdit 在其他格式上显示选择

AvalonEdit show selection over other formatting

我目前正在使用以下代码作为 LineTransformer 和 AvalonEdit TextEditor。我希望能够通过选择突出显示当前的单个搜索结果,但是该选择几乎不可见,因为 DocumentColorizingTransformer 的格式优先于显示突出显示的文本。如何让突出显示的选择显示而不是格式化或在格式化之前显示?

public class ColorizeSearchResults : DocumentColorizingTransformer {

    public ColorizeSearchResults() : base() {
        SearchTerm = "";
        MatchCase = false;
    }

    public string SearchTerm { get; set; }
    public bool MatchCase { get; set; }

    protected override void ColorizeLine(DocumentLine line) {
        if (SearchTerm.Length == 0)
            return;

        int lineStartOffset = line.Offset;
        string text = CurrentContext.Document.GetText(line);
        int count = 0;
        int start = 0;
        int index;
        while ((index = text.IndexOf(SearchTerm, start, MatchCase ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase)) >= 0) {
            base.ChangeLinePart(
                lineStartOffset + index,
                lineStartOffset + index + SearchTerm.Length,
                (VisualLineElement element) => {
                    element.TextRunProperties.SetForegroundBrush(Brushes.White);
                    element.TextRunProperties.SetBackgroundBrush(Brushes.Magenta);
                });
            start = index + 1;
            count++;
        }
    }
}

Example of formatting showing over selection

请参阅 http://avalonedit.net/documentation/html/c06e9832-9ef0-4d65-ac2e-11f7ce9c7774.htm 了解 AvalonEdit 渲染流程。

选择层在文本之前渲染。因此,如果文本有背景,它会覆盖选择背景。幸运的是,我们可以将背景设置为 Brush.Transparent(或 Selection.Brush 和您自己的颜色的混合)。

解决方案:我修改了 SelectionColorizer 代码以将选择背景重置为透明:

class SelectionColorizerWithBackground : ColorizingTransformer
{
    ICSharpCode.AvalonEdit.Editing.TextArea _textArea;

    public SelectionColorizerWithBackground(
        ICSharpCode.AvalonEdit.Editing.TextArea textArea)
    {
        if (textArea == null)
            throw new ArgumentNullException("textArea");
        this._textArea = textArea;
    }

    protected override void Colorize(ITextRunConstructionContext context)
    {
        int lineStartOffset = context.VisualLine.FirstDocumentLine.Offset;

        int lineEndOffset = context.VisualLine.LastDocumentLine.Offset +
            context.VisualLine.LastDocumentLine.TotalLength;

        foreach (var segment in _textArea.Selection.Segments)
        {
            int segmentStart = segment.StartOffset;
            if (segmentStart >= lineEndOffset)
                continue;

            int segmentEnd = segment.EndOffset;
            if (segmentEnd <= lineStartOffset)
                continue;

            int startColumn;
            if (segmentStart < lineStartOffset)
                startColumn = 0;
            else
                startColumn = context.VisualLine.ValidateVisualColumn(
                    segment.StartOffset, segment.StartVisualColumn,
                    _textArea.Selection.EnableVirtualSpace);

            int endColumn;
            if (segmentEnd > lineEndOffset)
                endColumn =
                    _textArea.Selection.EnableVirtualSpace
                        ? int.MaxValue
                        : context.VisualLine
                                 .VisualLengthWithEndOfLineMarker;
            else
                endColumn = context.VisualLine.ValidateVisualColumn(
                    segment.EndOffset, segment.EndVisualColumn,
                    _textArea.Selection.EnableVirtualSpace);

            ChangeVisualElements(
                startColumn, endColumn,
                element => {
                    element.TextRunProperties.SetBackgroundBrush(
                        System.Windows.Media.Brushes.Transparent);
                    if (_textArea.SelectionForeground != null)
                    {
                        element.TextRunProperties.SetForegroundBrush(
                            _textArea.SelectionForeground);
                    }
                });
        }
    }
}

要使用您应该执行以下操作的代码:

var lineTransformers = textEditor.TextArea.TextView.LineTransformers;

// Remove the original SelectionColorizer.
// Note: if you have syntax highlighting you need to do something else
// to avoid clearing other colorizers. If too complicated you can skip
// this step but to suffer a 2x performance penalty.
lineTransformers.Clear();

lineTransformers.Add(new ColorizeSearchResults());
lineTransformers.Add(
    new SelectionColorizerWithBackground(textEditor.TextArea));

在我广泛尝试了我的解决方案之后,我想补充几点:

  • 虽然我上面的其他解决方案似乎有效,但当应该平铺矩形时,您将有一些亚像素伪像。如果这是不可接受的,您可以实施 IBackgroundRenderer。 (这恰好是我选择的解决方案。)如果你想要一些代码,你可以在这里请求,但我怀疑它是否有用。

  • 顺便说一句,因为你的问题是关于搜索结果,很可能你可以使用 https://github.com/icsharpcode/AvalonEdit/blob/697ff0d38c95c9e5a536fbc05ae2307ec9ef2a63/ICSharpCode.AvalonEdit/Search/SearchResultBackgroundRenderer.cs 未修改的(或者如果你不修改它想要圆形边框)。

  • 您可以使用 element.BackgroundBrush = Brushes.Magenta; 而不是 element.TextRunProperties.SetBackgroundBrush(Brushes.Magenta);。 AvalonEdit 似乎用半径为 3px 的矩形绘制背景。

  • 从AvalonEdit 5.01开始还有RichTextColorizer。我不知道如何使用它,因为它没有在其他文件中引用。并且可能存在上一段中的(很可能不需要的)圆角矩形。

所以这是我的最终产品,几乎完全基于现有的 AvalonEdit SearchResultBackgroundRenderer

这与我的 post 的着色器有点不同,因为您必须手动修改搜索结果,而不是它为您做。但这也可能会节省一些计算时间。

如果您的搜索不使用正则表达式,那么您可以轻松修改 SearchResult,改为只为构造函数传入起始偏移量和长度。

/// <summary>A search result storing a match and text segment.</summary>
public class SearchResult : TextSegment {
    /// <summary>The regex match for the search result.</summary>
    public Match Match { get; }

    /// <summary>Constructs the search result from the match.</summary>
    public SearchResult(Match match) {
        this.StartOffset = match.Index;
        this.Length = match.Length;
        this.Match = match;
    }
}

/// <summary>Colorizes search results behind the selection.</summary>
public class ColorizeSearchResultsBackgroundRenderer : IBackgroundRenderer {

    /// <summary>The search results to be modified.</summary>
    TextSegmentCollection<SearchResult> currentResults = new TextSegmentCollection<SearchResult>();

    /// <summary>Constructs the search result colorizer.</summary>
    public ColorizeSearchResultsBackgroundRenderer() {
        Background = new SolidColorBrush(Color.FromRgb(246, 185, 77));
        Background.Freeze();
    }

    /// <summary>Gets the layer on which this background renderer should draw.</summary>
    public KnownLayer Layer {
        get {
            // draw behind selection
            return KnownLayer.Selection;
        }
    }

    /// <summary>Causes the background renderer to draw.</summary>
    public void Draw(TextView textView, DrawingContext drawingContext) {
        if (textView == null)
            throw new ArgumentNullException("textView");
        if (drawingContext == null)
            throw new ArgumentNullException("drawingContext");

        if (currentResults == 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 (SearchResult result in currentResults.FindOverlappingSegments(viewStart, viewEnd - viewStart)) {
            BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder();
            geoBuilder.AlignToWholePixels = true;
            geoBuilder.BorderThickness = 0;
            geoBuilder.CornerRadius = 0;
            geoBuilder.AddSegment(textView, result);
            Geometry geometry = geoBuilder.CreateGeometry();
            if (geometry != null) {
                drawingContext.DrawGeometry(Background, null, geometry);
            }
        }
    }

    /// <summary>Gets the search results for modification.</summary>
    public TextSegmentCollection<SearchResult> CurrentResults {
        get { return currentResults; }
    }

    /// <summary>Gets or sets the background brush for the search results.</summary>
    public Brush Background { get; set; }
}

为了使用后台渲染器:

var searchColorizor = new ColorizeSearchResultsBackgroundRenderer();
textEditor.TextArea.TextView.BackgroundRenderers.Add(searchColorizor);