whitespace COMPANY whitespace SERVICES whitespace PRODUCTS whitespace PURCHASE whitespace SUPPORT whitespace CONTACTS whitespace Home whitespace Contact Us whitespace Site Map whitespace
whitespace
WHY ARTFULBITS
whitespace
OUR APPROACH
whitespace
OUR TEAM
whitespace
FACT SHEET
whitespace
NEWS & EVENTS
whitespace
PRIVACY
whitespace
whitespace

Adorners in WPF

Downloads

Project archive #1
Project archive #2
Project archive #3
Project archive #4


What is an Adorner?

In WPF, an Adorner is special FrameworkElement that can be bounded to UIElement to allow user manipulate that element. Under manipulating we mean:

  • Adding functional handles to a UIElement that enable user to manipulate the element in some way (resize, rotate, reposition, etc.).
  • Provide visual feedback to indicate various states, or in response to various events.
  • Overlay visual decorations on a UIElement.
  • Visually mask or override part or whole UIElement.

WPF does not provide concrete adorners but it does provide the basic infrastructure. That means that you need to write your own special Adorner class.
A base infrastructure consists of next classes:

  • Adorner
    This is a base Adorner from which you will need to subclass.
  • AdornerLayer
    The AdornerLayer can be considered as a plane in which the Adorners are drawn.
  • AdornerDecorator
    Defines the location in visual tree of an AdornerLayer. Window class already has it in the visual tree. It adds the default adorner layer above all other controls in the window.

Adorning a Single UIElement

To bind an adorner to a particular UIElement, follow these steps:

  • Call the static method GetAdornerLayer of the AdornerLayer class to get an AdornerLayer object for the UIElement to be adorned. GetAdornerLayer walks up the visual tree, starting at the specified UIElement, and returns the first adorner layer it found. (If no adorner layers are found, the method returns null.)
  • Create new instance of the adorner you need and pass it the instance of the UIElement you need to adorn.
  • Call the Add method of the AdornerLayer, and pass it your new-ly created adorner to bind it to the target UIElement.

The following example binds  SomeAdorner (our own Adorner) to a TextBox named myTextBox.


myAdornerLayer = AdornerLayer.GetAdornerLayer( myTextBox );
myAdornerLayer.Add( new SomeAdorner( myTextBox ) );

Adorning the Children of a Panel

To bind an adorner to the children of a Panel, follow these steps:

1. Call the static method GetAdornerLayer to find an adorner layer for the element children of which are to be adorned.
2. Enumerate the children of the parent element and call the Add method to bind an adorner to each child element.

The following example binds a SomeAdorner (our own Adorne) to the children of a StackPanel named myStackPanel.


foreach( UIElement toAdorn in myStackPanel.Children ) 
  myAdornerLayer.Add( new SomeAdorner( toAdorn ) );

Rendering

Adorners are rendered in an AdornerLayer, which is a rendering surface that is always on top of the adorned element or a collection of adorned elements. Rendering of adorner is independent of rendering  UIElement the adorner is bound to. An adorner is typically positioned relatively to the element to which it is bound, using the standard 2-D coordinate origin located at the upper-left of the adorned element. Anything placed in the adorner layer is rendered on top of the rest visual elements. In other words, adorners are always visually on top and cannot be overridden using z-order.

It is important to note that adorners do not include any inherent rendering behavior (by default Adorner class does not render anything), ensuring that an adorner's rendering is the responsibility of the adorner's implementer.

This means that the adorner's rendering should be implemented by:

  1. Overriding the OnRenderSizeChanged method and drawing adorner's visual content using the DrawingContext object methods.
  2. Providing a visual children collection. (In this case you should override GetVisualChild method and VisualChildrenCount property)

The following examples accordingly show these approaches.

The first example adorner simply adorns the corners of a UIElement with circles.

// Adorners must subclass the abstract base class Adorner.
public class SimpleCircleAdorner : Adorner
{
    // Be sure to call the base class constructor.
    public SimpleCircleAdorner( UIElement adornedElement )
        : base(adornedElement){}
        
    // A common way to implement an adorner's rendering behavior is to override the OnRender
    // method, which is called by the layout system as part of a rendering pass.
    protected override void OnRender( DrawingContext drawingContext )
    {
        Rect adornedElementRect = new Rect( this.AdornedElement.DesiredSize );
        
        // Some arbitrary drawing implements.
        SolidColorBrush renderBrush = new SolidColorBrush( Colors.Green );
        renderBrush.Opacity = 0.2;
        Pen renderPen = new Pen( new SolidColorBrush( Colors.Navy ), 1.5 );
        double renderRadius = 5.0;
        
        // Draw a circle at each corner.
        drawingContext.DrawEllipse( renderBrush, renderPen, 
          adornedElementRect.TopLeft, renderRadius, renderRadius );
        drawingContext.DrawEllipse( renderBrush, renderPen, 
          adornedElementRect.TopRight, renderRadius, renderRadius );
        drawingContext.DrawEllipse( renderBrush, renderPen, 
          adornedElementRect.BottomLeft, renderRadius, renderRadius );
        drawingContext.DrawEllipse( renderBrush, renderPen, 
          adornedElementRect.BottomRight, renderRadius, renderRadius );
    }
}

The following image shows the SimpleCircleAdorner applied to a TextBox.

Sources of example above.

Second approach:

public class ResizingSelection : Adorner
{     
    // To store and manage the adorner's visual children.
    VisualCollection m_visualChildren;
    
    // Initialize the ResizingAdorner.
    public ResizingSelection( UIElement adornedElement )
        : base( adornedElement )
    {
        m_visualChildren = new VisualCollection( this );
        m_visualChildren.Add(...);
    }
    
    //Gets visual children count.
    protected override int VisualChildrenCount
    {
        get
        {
            return m_visualChildren.Count;
        }
    }
    
    //Gets visual by index.
    protected override Visual GetVisualChild( int index )
    {
        return m_visualChildren[ index ];
    }
}

An example above is simplified. Complete version is here.

Purpose of the AdornerDecorator

As I mentioned above, there is another class that plays an important role in rendering, called AdornerDecorator.
AdornerDecorator determines a placement of AdornerLayer in the visual tree. That divides a visual tree in two parallel branches one of which is the child of the AdornerDecorator with it's visual subtree, and the other one is AdornerLayer.

For better understanding we will consider the next example. Let us create a new project.
First of all lets implement our own Adorner class.

public class ControlAdorner: Adorner
{
    #region Private fields
    // Control that is the only child of the adorner and represents adorner.
    private HyperControl m_child;
    
    // Utilized for caching of offset by x co-ordinate.
    private double m_OffsetX = 0d;
    
    // Uri    private double m_OffsetY = 0d;
    #endregion
    
    #region Initialization
    public ControlAdorner( UIElement adornedElement )
        : base( adornedElement )
        {
            m_child = new HyperControl( this );
            AddLogicalChild( m_child );
            AddVisualChild( m_child );
        }
    #endregion
    
    #region Implementation
    //Measures content.
    protected override Size MeasureOverride( Size constraint )
    {
        m_child.Measure( constraint );
        return AdornedElement.RenderSize;
    }
    
    //Arranges child control to the full size.
    protected override Size ArrangeOverride( Size finalSize )
    {
        m_child.Arrange( new Rect( finalSize ) );
        return m_child.RenderSize;
    }
    
    public override GeneralTransform GetDesiredTransform( GeneralTransform transform )
    {
        GeneralTransformGroup group = new GeneralTransformGroup();
        group.Children.Add( transform );
        group.Children.Add( new TranslateTransform( OffsetX, OffsetY ) );
        return group;
    }
    
    //Get visual by index.
    protected override Visual GetVisualChild( int index ) 
    {
        return m_child;
    }
    
    private static void OnOffsetXChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
        ControlAdorner instance = (ControlAdorner)d;
        instance.OnOffsetXChanged( e );
    }
    
    private void OnOffsetXChanged( DependencyPropertyChangedEventArgs e )
    {
        m_OffsetX = (double)e.NewValue;
        if( OffsetXChanged != null )
        {
            OffsetXChanged( this, e );
        }
    }
    
    private static void OnOffsetYChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
        ControlAdorner instance = (ControlAdorner)d;
        instance.OnOffsetYChanged( e );
    }
    
    private void OnOffsetYChanged( DependencyPropertyChangedEventArgs e )
    {
        m_OffsetY = (double)e.NewValue;
        if( OffsetYChanged \!= null )
        {
            OffsetYChanged( this, e );
        }
    }
    #endregion
    
    #region Events
    public event PropertyChangedCallback OffsetXChanged;
    public event PropertyChangedCallback OffsetYChanged;
    public double OffsetX
    {
        get
        {
            return m_OffsetX;
        }
        set
        {
            SetValue( OffsetXProperty, value );
        }
    }
    public double OffsetY
    {
        get
        {
            return m_OffsetY;
        }
        set
        {
            SetValue( OffsetYProperty, value );
        }
    }
    
    // Gets visual children count, always 1.
    protected override int VisualChildrenCount
    {
        get
        {
            return 1;
        }
    }
    #endregion
    
    #region Dependency properties
    public static readonly DependencyProperty OffsetXProperty = DependencyProperty.Register( 
      "OffsetX", typeof( double ), typeof( ControlAdorner ),
      new FrameworkPropertyMetadata( 0d,    
        FrameworkPropertyMetadataOptions.AffectsArrange | 
        FrameworkPropertyMetadataOptions.AffectsParentArrange, 
        new PropertyChangedCallback( OnOffsetXChanged ) ) );
    
    public static readonly DependencyProperty OffsetYProperty = DependencyProperty.Register( 
      "OffsetY", typeof( double ), typeof( ControlAdorner ),
      new FrameworkPropertyMetadata( 0d, 
        FrameworkPropertyMetadataOptions.AffectsArrange |
        FrameworkPropertyMetadataOptions.AffectsParentArrange, 
        new PropertyChangedCallback( OnOffsetYChanged ) ) );
    #endregion
}

As you have already seen we used HyperContol class as a child of our Adorner. This class is also used to represent Adorner.
Here its implementation:

public class HyperControl : Control 
{
    // Creates new instance of the control and initializes it's DefaultStyleKey 
    // property with the type of the adorner.
    public HyperControl( Adorner adorner )
    {
        SetValue( DefaultStyleKeyProperty, adorner.GetType() );
    }
}

After above implementations we must create generic.xaml resource file where ControlTemplate and Style for HyperControl should be created.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WindowAdornerDecorator">
  <ControlTemplate x:Key="ControlAdornerTemplate" TargetType="{x:Type local:HyperControl}">
    <Border Name="border"  Background="Gray">
      <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" 
        Foreground="White" Text="Adorner"/>
    </Border>
    <ControlTemplate.Triggers>
      <Trigger Property="IsMouseOver" Value="True">
        <Setter TargetName="border" Property="Background"  Value="Red"/>
          </Trigger>
        </ControlTemplate.Triggers>
      </ControlTemplate>
    <Style x:Key="{x:Type local:ControlAdorner}" TargetType="{x:Type local:HyperControl}">
      <Setter Property="Template" Value="{StaticResource ControlAdornerTemplate}"/>
    </Style>
</ResourceDictionary>

Create GUI of our project in Window1.xaml.

<Window x:Class="WindowAdornerDecorator.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WindowAdornerDecorator" Height="300" Width="300">
  <Grid Background="AliceBlue" Height="200" Width="200">
    <TextBlock x:Name="textBlock" VerticalAlignment="Center" HorizontalAlignment="Center" 
      MouseDown="textBlock_MouseDown" FontFamily="Georgia" FontSize="18" Text=" Test "/>
    </Grid>
</Window>

Implement code-behind file.

namespace WindowAdornerDecorator
{
    public partial class Window1 : System.Windows.Window
    {
        public Window1()
        {
            InitializeComponent();
        }
        
        AdornerLayer adornerLayer;
        ControlAdorner controlAdorner;
        Point m_startPoint;
        
        void textBlock_MouseDown( object sender, MouseEventArgs e )
        {
            adornerLayer = AdornerLayer.GetAdornerLayer( textBlock );
            controlAdorner = new ControlAdorner( textBlock );
            m_startPoint = Mouse.GetPosition( textBlock );
            adornerLayer.Add( controlAdorner );
            controlAdorner.MouseMove += new MouseEventHandler( controlAdorner_MouseMove );
            controlAdorner.MouseUp += new MouseButtonEventHandler( controlAdorner_MouseUp );
        }
        
        void controlAdorner_MouseUp( object sender, MouseButtonEventArgs e )
        {
            adornerLayer.Remove( controlAdorner );
        }
        
        void controlAdorner_MouseMove( object sender, MouseEventArgs e )
        {
            if( e.LeftButton == MouseButtonState.Pressed )
            {
                Point currentPoint = e.GetPosition( textBlock );
                controlAdorner.OffsetX = currentPoint.X - m_startPoint.X;
                controlAdorner.OffsetY = currentPoint.Y - m_startPoint.Y;
            }
        }
    }
}

Run that project and start dragging TextBlock with Text property set to "Test". You can see that adorner can be draged above the entire Window.

You can get that project here.

After that lets change Window1.xaml by adding AdornerDecorator in the Grid. And sets its ClipToBounds property to true:

<Window x:Class="WindowAdornerDecorator.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WindowAdornerDecorator" Height="300" Width="300">
  <Grid Background="AliceBlue" Height="200" Width="200">
    <AdornerDecorator ClipToBounds="True">
      <TextBlock x:Name="textBlock" VerticalAlignment="Center" HorizontalAlignment="Center" 
      MouseDown="textBlock_MouseDown" FontFamily="Georgia" FontSize="18" Text=" Test "/>
    </AdornerDecorator>
  </Grid>
</Window>

Run that, and you will notice that adorner can be dragged only inside the grid element.

Source files of the project can be found here.

So, I hope that after that experience you will understand the purpose of AdornerDecorator class.

Note: If you need to switch adorner layer and adorned element in z-order, you should derrive from AdornerDecorator class and override GetVisualChild method (it's default implementation can be discovered using the awesome tool by Lutz Roeder's, named Reflector).

Events and Hit Testing

Adorners receive input events just like any other FrameworkElement. As the adorner always has a higher z-order than the element it adorns, the adorner receives input events that may be intended for the underlying adorned element. An adorner can listen for certain input events and pass these on to the underlying adorned element by re-raising the event.

To enable pass-through hit testing of elements under an adorner, set the hit test IsHitTestVisible property to false on the adorner.


< Previous | Home | Next >


Author: 2007 Dima Zaharov, Department Manager/ArtfulBits
Company | Services | Practices | Technologies | Career | Contacts | Privacy
© 2005-2014 ArtfulBits. All rights reserved.