I have created an adorner which it to be applied to a Textbox and this is for our custom built Spell-Checking. Whenever there is an Unknown Word, the adorner will add an underline using drawingcontext.drawline. Here is the code:
_highlighterPen = new Pen(new SolidColorBrush(Colors.Red), 2)
{
EndLineCap = PenLineCap.Square,
StartLineCap = PenLineCap.Square
};
private void HighlightWord(DrawingContext drawingContext, int characterIndex, int wordLength)
{
if (characterIndex + wordLength > _textbox.Text.Length)
return;
var start = _textbox.GetRectFromCharacterIndex(characterIndex);
var end = _textbox.GetRectFromCharacterIndex(characterIndex + (wordLength - 1), true);
if (start.Y < 0 || start.BottomRight.Y > _textbox.ActualHeight)
return;
var wrapCorrectedStartRect = start.BottomRight.Y != end.BottomRight.Y ? new Rect(2, start.Y, start.Width, start.Height) : start;
var highlightArea = Rect.Union(wrapCorrectedStartRect, end);
drawingContext.DrawLine(_highlighterPen, highlightArea.BottomLeft, highlightArea.BottomRight);
if (start.BottomRight.Y != end.BottomRight.Y)
HighlightWrappedWord(drawingContext, characterIndex, end, wordLength);
}
What I was hoping to achive was a Mouse Over effect for the line (without affecting the other lines), without having to invalidate the visual and redraw everything again. Please can anyone help on this?
You can override UIElement.OnMouseEnter
and UIElement.OnMouseLeave
on your adorner.
Because you can't modify the old DrawingContext
you would have to invalidate the Adorner
to add new content to the DrawingContex
.
If the changes are not too complex, like adding new drawings, and you only want to change referenced resources like brushes an pens, you can define those resources as members and swap them in and out on UIElement.MoueseEnter
and UIElement.MouseLeave
. Note, this will only work for resources that implement Freezable
e.g. like Pen
and SolidColorBrush
. This is because Freezable
provides its own property changed mechanism which is used by the rendering engine to track property changes.
The following example shows how to enable the Adorner
to change drawing resources dynamically:
private class TextBoxAdorner : Adorner
{
private static readonly SolidColorBrush DefaultHighlightBrush;
private static readonly SolidColorBrush MouseOverHighlightBrush;
private static readonly Pen HighlighterPen;
static TextBoxAdorner()
{
// Initialize from constructor so that we can freeze the resources
// to enable a much better performance
TextBoxAdorner.DefaultHighlightBrush = new SolidColorBrush(Colors.Green);
TextBoxAdorner.DefaultHighlightBrush.Freeze();
TextBoxAdorner.MouseOverHighlightBrush = new SolidColorBrush(Colors.Red);
TextBoxAdorner.MouseOverHighlightBrush.Freeze();
// Don't freeze the pen as we need to modify it
// and the Freezable should report those changes to the rendering engine
TextBoxAdorner.HighlighterPen = new Pen()
{
Brush = TextBoxAdorner.DefaultHighlightBrush,
EndLineCap = PenLineCap.Square,
StartLineCap = PenLineCap.Square
};
}
public TextBoxAdorner(UIElement adornedElement) : base(adornedElement)
{
// IsHitTestVisible is TRUE by default.
// This line is added to emphasize that the property must be TRUE
// in order to enable the adorner to receive input events
this.IsHitTestVisible = true;
}
// Show mouse hover effects
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
// Set the mouse over effects.
//
// Because Pen is a freezable (unfrozen)
// any changes are noticed by the rendering engine
TextBoxAdorner.HighlighterPen.Brush = TextBoxAdorner.MouseOverHighlightBrush;
}
// Hide mouse hover effects
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
// Clear the mouse over effects.
//
// Because Pen is a freezable (unfrozen)
// any changes are noticed by the rendering engine
TextBoxAdorner.HighlighterPen.Brush = TextBoxAdorner.DefaultHighlightBrush;
}
// Render the Adorner itself
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
drawingContext.DrawLine(TextBoxAdorner.HighlighterPen, new Point(0, 0), new Point(200, 0));
}
}