I'm using AvalonEdit, and I want the user to always be able to see which line the caret is on, even when the editor does not have focus. To that end, I've found and adapted some code that uses a BackgroundRenderer to highlight the background of the current line.
Unfortunately, if I change the CaretOffset while the editor isn't focused, my background rectangle stays put, on the line that was current when the editor lost focus. It doesn't sync up to the new current line until the editor gets focus again.
I figured out why this is happening (just not how to fix it). Per the doc comments for IBackgroundRenderer, "Background renderer will draw only if their associated known layer chooses to draw them. For example, background renderers in the caret layer will be invisible when the caret is hidden." My background renderer lives on KnownLayer.Caret, so yes, I see why it's not updating when the editor isn't focused -- it's because the caret is hidden too. (Given that, I'm actually surprised that my rectangle stays visible.)
I tried explicitly calling textEditor.TextArea.TextView.InvalidateLayer(KnownLayer.Caret) immediately after I set the CaretOffset, but that had no effect -- I'm guessing that the call was ignored because the caret was hidden.
What's the best way to force my current-row highlight to update even when the editor doesn't have focus?
Here's the code for my class. I'm certainly open to throwing this away and doing a different approach if there's a better way to do it.
public class HighlightCurrentLineBackgroundRenderer : IBackgroundRenderer
{
private TextEditor _editor;
public HighlightCurrentLineBackgroundRenderer(TextEditor editor)
{
_editor = editor;
}
public KnownLayer Layer
{
get { return KnownLayer.Caret; }
}
public void Draw(TextView textView, DrawingContext drawingContext)
{
if (_editor.Document == null)
return;
textView.EnsureVisualLines();
var currentLine = _editor.Document.GetLineByOffset(_editor.CaretOffset);
foreach (var rect in BackgroundGeometryBuilder.GetRectsForSegment(textView, currentLine))
{
drawingContext.DrawRectangle(
new SolidColorBrush(Color.FromArgb(0x40, 0, 0, 0xFF)), null,
new Rect(rect.Location, new Size(textView.ActualWidth - 32, rect.Height)));
}
}
}
Then in my UserControl's constructor, I add the renderer to the editor:
textEditor.TextArea.TextView.BackgroundRenderers.Add(
new HighlightCurrentLineBackgroundRenderer(textEditor));
Here's how I wound up making this work.
First, I changed HighlightCurrentLineBackgroundRenderer's Layer property to return KnownLayer.Background. The background layer is visible even when the editor doesn't have focus, so that solves the original problem.
However, it introduces a new problem: the background layer only gets redrawn under certain conditions, and "caret moved" isn't one of those conditions -- so now the highlight doesn't move at all! (Well, it does -- for example, when you scroll, or when you select text. But that's not the desired behavior.) But that's easily solved; I just need to manually invalidate the background layer whenever the caret moves:
That's all there is to it -- now the highlight updates even if the editor isn't focused.