Click here to Skip to main content
15,883,915 members
Articles / Desktop Programming / WPF

Design Adorners in XAML with Data Binding Support

Rate me:
Please Sign up or sign in to vote.
4.89/5 (7 votes)
20 Jan 2014CPOL4 min read 39.4K   1.7K   22   5
Presents a few simple controls to help you design adorners with data binding support using only XAML

Introduction

This article presents the SmartAdorner class that allows you to design and attach adorner to any visual element in WPF using only XAML. The adorner content is defined in a data template allowing data-binding to the adorned element’s data context. In this way, properties from the bound model (or view model) are used and manipulated in the adorner elements in the same as in the adorned element.

To further support the design of adorners, the article code also includes a Thumb-derived control that allows manipulation of coordinates using only data binding and a general control to support resizing handles in the corners.

Background

Adorners in WPF are decorations on existing elements shown in an adorner layer above other visual elements. Typical use for adorners is for dragging/resize handles on selected elements. Without the adorner layer, such handles would sometimes be hidden behind other objects, making it more difficult or even impossible for the user to interact with them. The SmartAdorner solution presented here is general and supports other types of handles and dynamic and interactive decorations too.

An adorner is always associated with an adorned element which has to be passed as an argument to the Adorner constructor. Thus it is not possible to create adorners directly in WPF, without help from a helper, such as the SmartAdorner presented here. SmartAdorner derives from Adorner but has attached properties to allow definition of the XAML content in a data template and to dynamically show and hide the adorner.

When searching the internet for existing solutions, I did not find anyone as simple as this one, also supporting data binding.

Using the Code

Add a General Adorner

To specify the content for the adorner of an element, you simply set the SmartAdorner.Template attached property like this:

XML
<StackPanel Name="test" a:SmartAdorner.Visible="{Binding IsSelected}" >
   <Rectangle Width="64" Height="64" Stroke="Black" Fill="White" />
   <TextBlock Text="{Binding Text}" />  
   <a:SmartAdorner.Template>
         <DataTemplate DataType="{x:Type local:IconViewModel}" >
             <Canvas>
                <a:DragThumb Name="IconThumb" Canvas.Left="-6" 
                Canvas.Top="-6" Width="12" 
                Height="12" X="{Binding X}" Y="{Binding Y}"  />
                <TextBox Canvas.Top="64" T
                ext="{Binding Text, UpdateSourceTrigger=PropertyChanged}" />
              </Canvas>
         </DataTemplate>
    </a:SmartAdorner.Template>
</StackPanel>

In this example, we adorn the StackPanel using a Canvas containing a thumb control and a textbox. As demonstrated, data bindings can be used. To make the adorner visible, the SmartAdorner.Visible has to be set to true. In this example, we use data binding to set this dynamically when the item is selected in the containing list box.

When designing the adorner, it is good to know that the data template context is layout in a ContentPresenter in the adorner layer whose position, size and layout and rendering transformation is automatically adjusted to the layout of the adorned element. Here, a Canvas is used to allow exact position of elements, even outside the boundaries of the adorned element, but any element can be used inside the SmartAdorner's data template.

For advanced usage, SmartAdorner also supports dynamically selecting adorner template based on the data object through the SmartAdorner.TemplateSelector attached property.

Adding Dragging Thumbs

A common usage for adorners is to provide dragging and resize handles. The in-built Thumb class can be used for that provided that at least a DragDelta event handler is written in code behind. The article code includes a derived version of Thumb, DragThumb, that allows any pair of properties (or just a single dimension) to be changed whenever dragged. The dependency properties X and Y in DragThumb allows binding to properties directly in XAML without writing any code-behind. In the sample project, this is used to be able to manipulate a line object with properties X1, Y1, X2 and Y2:

XML
<a:SmartAdorner.Template>
    <DataTemplate DataType="{x:Type local:LineViewModel}" >
       <Canvas>
          <a:DragThumb Canvas.Left="{Binding X1}" 
          Canvas.Top="{Binding Y1}" Margin="-6" 
                       X="{Binding X1}" 
                       Y="{Binding Y1}" Width="12" 
                       Height="12" />
          <a:DragThumb Canvas.Left="{Binding X2}" 
          Canvas.Top="{Binding Y2}" Margin="-6" 
                       X="{Binding X2}" Y="{Binding Y2}" 
                       Width="12" Height="12"/>
       </Canvas>
    </DataTemplate><br /> </a:SmartAdorner.Template>

Adding Resizing Handles

Since a common usage of adorners is to provide resize handles in the corners, I created a control that can simplify that. Just put the ResizingAdorner inside the adorner template and bind the properties you want to manipulate to X, Y, Width and Height like this:

XML
<a:SmartAdorner.Template>
   <DataTemplate DataType="{x:Type local:RectViewModel}" >
      <a:ResizingAdorner X="{Binding X}" Y="{Binding Y}" 
          Width="{Binding Width}" Height="{Binding Height}" 
          MinWidth="10" MinHeight="20" 
          MaxWidth="200" MaxHeight="400" />   
  </DataTemplate>
</a:SmartAdorner.Template>

As shown in the code-snippet, ResizingAdorner supports minimum and maximum constraints of the width and height.

Since ResizingAdorner is a content control arbitrary content can be put inside it and if you want you can use it outside an adorner template as well.

By default, ordinary Thumbs are shown in the corners, but since ResizingAdorner is designed as a look-less custom control, it supports complete restyling by specifying a new control template.

Points of Interest

Efficient Implementation of SmartAdorner

The SmartAdorner class derives from Adorner and defines the attached properties used to set the template and make adorner visible. It also uses the private attached property Adorner to store a reference to the adorner created just when Visible is set to true in the property change callback, as shown below:

C#
private static void OnVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    FrameworkElement adornedElement = d as FrameworkElement;
    if (adornedElement == null) throw new InvalidOperationException("Adorners can only be applied to elements deriving from FrameworkElement");
    AdornerLayer layer = AdornerLayer.GetAdornerLayer(adornedElement);
    if (layer == null) throw new InvalidOperationException("Cannot show adorner since no adorner layer was found in the visual tree");
            
    SmartAdorner adorner = GetAdorner(adornedElement);
            
    bool isVisible = (bool)e.NewValue;

    if (isVisible && adorner == null)
    {
         adorner = new SmartAdorner(adornedElement);
                
         SetAdorner(adornedElement, adorner);
         layer.Add(adorner);
     }
     else if( !isVisible && adorner != null )
     {
         layer.Remove(adorner);
         SetAdorner(adornedElement, null);
      }
} 

The SmartAdorner uses a ContentPresenter as a single child, whose data context and template is initialized in the constructor:

C#
private ContentPresenter presenter;

public SmartAdorner(FrameworkElement adornedElement)
  : base(adornedElement)
{
    presenter = new ContentPresenter();
    Binding dataContextBinding = new Binding("DataContext");
    dataContextBinding.Source = adornedElement;
    BindingOperations.SetBinding(presenter, ContentPresenter.ContentProperty, dataContextBinding);
    Template = GetTemplate(adornedElement);
    TemplateSelector = GetTemplateSelector(adornedElement);
    AddVisualChild(presenter);
    AddLogicalChild(presenter);
}
        
public DataTemplate Template
{
    get { return presenter.ContentTemplate; }
    set { presenter.ContentTemplate = value; }
}

public DataTemplateSelector TemplateSelector
{
    get { return presenter.ContentTemplateSelector; }
    set { presenter.ContentTemplateSelector = value; }
}

The rest is just some method that has to be overridden to layout the single child.

C#
protected override int VisualChildrenCount
{
    get
    {
        return 1;
    }
}

protected override System.Windows.Media.Visual GetVisualChild(int index)
{
    if (index == 0) return presenter;
    throw new ArgumentOutOfRangeException("index");
}

protected override Size MeasureOverride(Size constraint)
{
    presenter.Measure(constraint);
    return presenter.DesiredSize;
}

protected override Size ArrangeOverride(Size finalSize)
{
    presenter.Arrange(new Rect(new Point(0,0), finalSize)); 
   return finalSize;
}

Conclusion

In this article, you learned an efficient implementation of a general adorner class that allows you do define new adorners in XAML with support for data binding. The solution is quite general and efficient. Although it may not be not a complete solution for building a design Surface, I hope you find good use of it.

History

  • 20 January, 2014 – Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Sweden Sweden
Henrik Jonsson is a Microsoft Professional Certified Windows Developer (MCPD) that currently works as an IT consultant in Västerås, Sweden.

Henrik has worked in several small and large software development projects in various roles such as architect, developer, CM and tester.

He regularly reads The Code Project articles to keep updated about .NET development and get new ideas. He has contributed with articles presenting some useful libraries for Undo/Redo, Dynamic Linq Sorting and a Silverlight 5 MultiBinding solution.

Comments and Discussions

 
Questionrequest Pin
aliwpf21-Jan-14 0:50
aliwpf21-Jan-14 0:50 
AnswerRe: request Pin
Henrik Jonsson21-Jan-14 3:33
Henrik Jonsson21-Jan-14 3:33 
GeneralRe: request Pin
aliwpf21-Jan-14 4:05
aliwpf21-Jan-14 4:05 
GeneralRe: request Pin
Henrik Jonsson21-Jan-14 4:42
Henrik Jonsson21-Jan-14 4:42 
GeneralRe: request Pin
aliwpf21-Jan-14 6:02
aliwpf21-Jan-14 6:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.