wpfmouseoveradorner

WPF Adorner MouseOver


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?


Solution

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