0

I am relatively new to C# and I'm working on a simple application that makes use of AvalonEdit as a small code editor embedded in the bottom of the UI.

I want to highlight all occurrences of a selected word. Right now I have this working using a DocumentColorizingTransformer as outlined by Suplanus in this question.

My code is basically identical to the implementation posted there, but it has its downsides. Mainly that the default highlighting is fighting with the new LineTransformers highlighting and there are visual artifacts that can't be avoided.

So I came across this question where Siegfried (partially) explains a way to accomplish this by using an IBackgroundRenderer class. Unfortunately there isn't a lot of code to go on. Just an outline of how to structure the draw function. There is another question where trigger_segfault posts some similar code utilizing an IBackgroundRenderer but the goal is not to highlight matching words, but search results.

So I've kind of cobbled together what I can between all of these questions and arrived here:

(Removed. See bottom of post for working code)

My question is simply, how do I pass in the matching words? Do I assign an event handler to the "SelectionChanged" event: txtSvgSource.TextArea.SelectionChanged += ValidateSelection; and then implement a setter in my HighlightSelectedWordBackgroundRenderer class to update currentResults?

I just don't understand how to pass in the words to highlight.

I'd like to move away from my current DocumentColorizingTransformer approach and properly implement the IBackgroundRenderer instead.

Anyone with experience using AvalonEdit that could point me in the right direction, your help would be extremely appreciated!

Thanks so much.

Edit 2:


I figured everything out. Here's the final working code if anyone finds this useful:

using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;

namespace SVGRecolorTool {

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

    public class HighlightSelectedWordBackgroundRenderer : IBackgroundRenderer {

        private TextEditor _editor;

        TextSegmentCollection<SearchResult> currentResults = new TextSegmentCollection<SearchResult>();

        public HighlightSelectedWordBackgroundRenderer(TextEditor editor) {
            _editor = editor;
            Background = new SolidColorBrush(Color.FromArgb(90, 255, 255, 255));
            Background.Freeze();
        }

        public KnownLayer Layer {
            get { return KnownLayer.Caret; }
        }

        public void Draw(TextView textView, DrawingContext drawingContext) {

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

        public TextSegmentCollection<SearchResult> CurrentResults {
            get { return currentResults; }
        }

        public Brush Background { get; set; }
    }
}
_renderer = new HighlightSelectedWordBackgroundRenderer(txtSvgSource);
txtSvgSource.TextArea.TextView.BackgroundRenderers.Add(_renderer);
txtSvgSource.TextArea.SelectionChanged += HighlightSelection;
private void HighlightSelection(object sender, EventArgs e) {

    _renderer.CurrentResults.Clear();

    if(txtSvgSource.SelectedText.Length > 2) {
        string pattern = @"\b" + txtSvgSource.SelectedText + @"\b";
        Regex rg = new Regex(pattern);
        MatchCollection matchedSelections = rg.Matches(txtSvgSource.Text);

        if (matchedSelections.Count > 1) {
            foreach (Match result in matchedSelections) {
                _renderer.CurrentResults.Add(new SearchResult(result));
            }
        } else {
            _renderer.CurrentResults.Clear();
        }

    } else {
        _renderer.CurrentResults.Clear();
    }     

    txtSvgSource.TextArea.TextView.InvalidateLayer(KnownLayer.Selection);
}
fmotion1
  • 237
  • 4
  • 12
  • CurrentResults is implemented as a public property so you have access to the collection. So clear it and add the results you want highlighted. I believe in your case that listening to the SelectionChanged event would probably work, try it. – Jim Foye Aug 04 '22 at 16:22
  • @JimFoye Thanks for that, I managed to get it working (partially). If you don't mind looking at the edit above, I could use some help with my regex and matching logic. I'm getting really close to a working solution. – fmotion1 Aug 04 '22 at 18:04
  • You didn't need to add a setter for the collection, you can just clear it and add to it. – Jim Foye Aug 04 '22 at 19:31
  • I suggest you create a separate question for the regex, you can provide some examples of text that you are expecting to match, but aren't (if that's the problem you are having). One thing, though, you probably need to escape the text you are grabbing (Regex.Escape()). – Jim Foye Aug 04 '22 at 19:33
  • @JimFoye thank you for the heads up. I am new to the language and missed the `List.Clear` and `List.Add` methods. I thought without a setter that the property was read-only. Thank you again for pointing that out. I also solved my Regex issue so this question is pretty much in the bag. – fmotion1 Aug 09 '22 at 00:24

0 Answers0