Adorners

 


An adorner is a special kind of class that is rendered above all UI controls, in what is known as an adorner layer. Adorner elements in this layer will always be rendered on top of the normal WPF controls.

For example we could use adorner to add handles to a UI control, to enable users to resize the element.

Allow user to resize Rectangle elements on a canvas


Adorners need to be initialized in code and so, a good place to do this is in the UserControl.Loaded method, when we can be certain that the canvas and its items will have been initialized. 

1. Create class ResizingAdorner.cs and inherit from Adorner class

 public class ResizingAdorner : Adorner 
  { 
    private VisualCollection visualChildren; 
    private Thumb top, left, bottom, right; 

2. Declare the VisualCollection object that will contain the visuals that we want to render

3. Declare 4 Thumb elements for top, right, bottom, left provide a DragDelta event that we will use to register the users' mouse movements when they drag each Thumb: HorizontalChange or VerticalChange properties of the DragDeltaEventArgs object to specify the distance and direction of the mouse movement that triggered the event.

 private Thumb InitializeThumb(Cursor cursor, DragDeltaEventHandler eventHandler) 
    { 
      Thumb thumb = new Thumb(); 
      thumb.BorderBrush = Brushes.Black; 
      thumb.BorderThickness = new Thickness(1); 
      thumb.Cursor = cursor; 
      thumb.DragDelta += eventHandler; 
      thumb.Height = thumb.Width = 6.0; 
      visualChildren.Add(thumb); 
      return thumb; 
    } 
 

 Constructor:

public ResizeAdorner(UIElement adornedElement) : base(adornedElement) 
    { 
      visualChildren = new VisualCollection(this); 
      top = InitializeThumb(Cursors.SizeNS, Top_DragDelta); 
      left = InitializeThumb(Cursors.SizeWE, Left_DragDelta); 
      bottom = InitializeThumb(Cursors.SizeNS, Bottom_DragDelta); 
      right = InitializeThumb(Cursors.SizeWE, Right_DragDelta); 
    } 

4. Handle DragDelta events and  change adorner element (which is layer on top of our rectangles) height or width  and use the HorizontalChange or VerticalChange that specify the distance and direction of the mouse movement.

private void Top_DragDelta(object sender, DragDeltaEventArgs e) 
    { 
      var adornedElement = (FrameworkElement)AdornedElement; 
      adornedElement.Height = adornedElement.Height - e.VerticalChange; 
      Canvas.SetTop(adornedElement, 
        Canvas.GetTop(adornedElement) + e.VerticalChange); 
    } 
 
    private void Left_DragDelta(object sender, DragDeltaEventArgs e) 
    { 
      var adornedElement = (FrameworkElement)AdornedElement; 
      adornedElement.Width = adornedElement.Width - e.HorizontalChange; 
      Canvas.SetLeft(adornedElement, 
        Canvas.GetLeft(adornedElement) + e.HorizontalChange); 
    } 
 
    private void Bottom_DragDelta(object sender, DragDeltaEventArgs e) 
    { 
      var adornedElement = (FrameworkElement)AdornedElement; 
      adornedElement.Height =adornedElement.Height + e.VerticalChange; 
    } 
 
    private void Right_DragDelta(object sender, DragDeltaEventArgs e) 
    { 
      var adornedElement = (FrameworkElement)AdornedElement; 
      adornedElement.Width = adornedElement.Width + e.HorizontalChange; 
    } 

5. Render Adorner Visuals. This will  draw a transparent rectangle with  blue borders that is large by 2 pixels of our rectangles and represented by AdornedElement.DesiredSize property.

  protected override void OnRender(DrawingContext drawingContext) 
    { 
      var brush = new SolidColorBrush(Colors.Transparent); 
      var pen = new Pen(new SolidColorBrush(Colors.DeepSkyBlue), 1.0); 
      drawingContext.DrawRectangle(brush, pen,  
        new Rect(-2, -2, AdornedElement.DesiredSize.Width + 4, 
        AdornedElement.DesiredSize.Height + 4)); 
    } 


6. Arrange Thumbs  (x,y, width, height) in the ArrangeOverride method, we use the .NET Framework to render Visual elements using their Arrange methods, as we would in a custom panel

protected override Size ArrangeOverride(Size finalSize) 
    { 
      top.Arrange(
        new Rect(AdornedElement.DesiredSize.Width / 2 - 3, -8, 6, 6)); 
      left.Arrange(
        new Rect(-8, AdornedElement.DesiredSize.Height / 2 - 3, 6, 6)); 
      bottom.Arrange(new Rect(AdornedElement.DesiredSize.Width / 2 - 3, 
        AdornedElement.DesiredSize.Height + 2, 6, 6)); 
      right.Arrange(new Rect(AdornedElement.DesiredSize.Width + 2,  
        AdornedElement.DesiredSize.Height / 2 - 3, 6, 6)); 
      return finalSize;
    }

7.  When a derived class has multiple visuals to render, it is a requirement of the layout system that the correct number of visuals is specified. For example, if we always returned the value 2 from this property, then only two of our thumbs would be rendered on screen.

 protected override int VisualChildrenCount 
    { 
      get { return visualChildren.Count; } 
    } 
 
    protected override Visual GetVisualChild(int index) 
    { 
      return visualChildren[index]; 
    } 

  Code Source