Adorner is a special framework element which is bound to
UIElement. You can check WPF
Class Hierarchy for more about WPF controls. Adorners is the way to extend controls
via adding extra visual functionality.
WPF internally uses adorners in validating controls, you must have seen
red border on controls whose validation failed. The red border is adorner layer
which is placed on top of the control. You can check my article How
to validate data in WPF to know more about validations.
Adorners are bound to UIElement and placed top of it and independent
of rendering of UIElement. You can add additional functionalities of UIElement
like resize, move, rotate etc via adding Adorner layer to UIElement. Adorners
are visual decoration to decorate UIElement.
See below example which uses adorner to select/resize
UIElement on canvas.
XAML
<Window x:Class="AdornerExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:AdornerExample"
mc:Ignorable="d"
Title="MainWindow" Height="350"
Width="525">
<Canvas Name="myCanvas">
<Button Height="50" Width="150" Content="Click Me!"
Canvas.Left="50" Canvas.Top="50"
Name="myButton" />
<TextBox Height="25" Width="120"
Text="TextBox"
Canvas.Top="200" Canvas.Left="100" />
</Canvas>
</Window>
Code –
Custom adorner for decorating UIElement
public class BorderAdorner : Adorner
{
//use thumb for
resizing elements
Thumb topLeft, topRight,
bottomLeft, bottomRight;
//visual child
collection for adorner
VisualCollection visualChilderns;
public BorderAdorner(UIElement element) : base(element)
{
visualChilderns = new VisualCollection(this);
//adding thumbs for
drawing adorner rectangle and setting cursor
BuildAdornerCorners(ref topLeft, Cursors.SizeNWSE);
BuildAdornerCorners(ref topRight, Cursors.SizeNESW);
BuildAdornerCorners(ref bottomLeft, Cursors.SizeNESW);
BuildAdornerCorners(ref bottomRight, Cursors.SizeNWSE);
//registering drag
delta events for thumb drag movement
topLeft.DragDelta += TopLeft_DragDelta;
topRight.DragDelta += TopRight_DragDelta;
bottomLeft.DragDelta += BottomLeft_DragDelta;
bottomRight.DragDelta += BottomRight_DragDelta;
}
private void BottomRight_DragDelta(object sender, DragDeltaEventArgs e)
{
FrameworkElement adornedElement = this.AdornedElement as FrameworkElement;
Thumb bottomRightCorner = sender as Thumb;
//setting new height
and width after drag
if (adornedElement != null && bottomRightCorner
!= null)
{
EnforceSize(adornedElement);
double oldWidth = adornedElement.Width;
double oldHeight = adornedElement.Height;
double newWidth = Math.Max(adornedElement.Width
+ e.HorizontalChange, bottomRightCorner.DesiredSize.Width);
double newHeight = Math.Max(e.VerticalChange
+ adornedElement.Height , bottomRightCorner.DesiredSize.Height);
adornedElement.Width = newWidth;
adornedElement.Height = newHeight;
}
}
private void TopRight_DragDelta(object sender, DragDeltaEventArgs e)
{
FrameworkElement adornedElement = this.AdornedElement as FrameworkElement;
Thumb topRightCorner = sender as Thumb;
//setting new
height, width and canvas top after drag
if (adornedElement != null && topRightCorner !=
null)
{
EnforceSize(adornedElement);
double oldWidth = adornedElement.Width;
double oldHeight = adornedElement.Height;
double newWidth = Math.Max(adornedElement.Width
+ e.HorizontalChange, topRightCorner.DesiredSize.Width);
double newHeight = Math.Max(adornedElement.Height
- e.VerticalChange, topRightCorner.DesiredSize.Height);
adornedElement.Width = newWidth;
double oldTop = Canvas.GetTop(adornedElement);
double newTop = oldTop - (newHeight - oldHeight);
adornedElement.Height = newHeight;
Canvas.SetTop(adornedElement, newTop);
}
}
private void TopLeft_DragDelta(object sender, DragDeltaEventArgs e)
{
FrameworkElement adornedElement = this.AdornedElement as FrameworkElement;
Thumb topLeftCorner = sender as Thumb;
//setting new
height, width and canvas top, left after drag
if (adornedElement != null && topLeftCorner != null)
{
EnforceSize(adornedElement);
double oldWidth = adornedElement.Width;
double oldHeight = adornedElement.Height;
double newWidth = Math.Max(adornedElement.Width
- e.HorizontalChange, topLeftCorner.DesiredSize.Width);
double newHeight = Math.Max(adornedElement.Height
- e.VerticalChange, topLeftCorner.DesiredSize.Height);
double oldLeft = Canvas.GetLeft(adornedElement);
double newLeft = oldLeft - (newWidth - oldWidth);
adornedElement.Width = newWidth;
Canvas.SetLeft(adornedElement,
newLeft);
double oldTop = Canvas.GetTop(adornedElement);
double newTop = oldTop - (newHeight - oldHeight);
adornedElement.Height = newHeight;
Canvas.SetTop(adornedElement, newTop);
}
}
private void BottomLeft_DragDelta(object sender, DragDeltaEventArgs e)
{
FrameworkElement adornedElement = this.AdornedElement as FrameworkElement;
Thumb topRightCorner = sender as Thumb;
//setting new
height, width and canvas left after drag
if (adornedElement != null && topRightCorner !=
null)
{
EnforceSize(adornedElement);
double oldWidth = adornedElement.Width;
double oldHeight = adornedElement.Height;
double newWidth = Math.Max(adornedElement.Width
- e.HorizontalChange, topRightCorner.DesiredSize.Width);
double newHeight = Math.Max(adornedElement.Height
+ e.VerticalChange, topRightCorner.DesiredSize.Height);
double oldLeft = Canvas.GetLeft(adornedElement);
double newLeft = oldLeft - (newWidth - oldWidth);
adornedElement.Width = newWidth;
Canvas.SetLeft(adornedElement, newLeft);
adornedElement.Height = newHeight;
}
}
public void BuildAdornerCorners(ref Thumb cornerThumb, Cursor customizedCursors)
{
//adding new thumbs
for adorner to visual childern collection
if (cornerThumb != null) return;
cornerThumb = new Thumb() { Cursor =
customizedCursors, Height = 10, Width = 10, Opacity = 0.5, Background = new SolidColorBrush(Colors.Red) };
visualChilderns.Add(cornerThumb);
}
public void EnforceSize(FrameworkElement element)
{
if (element.Width.Equals(Double.NaN))
element.Width =
element.DesiredSize.Width;
if (element.Height.Equals(Double.NaN))
element.Height =
element.DesiredSize.Height;
//enforce size of element not exceeding
to it's parent element size
FrameworkElement parent = element.Parent as FrameworkElement;
if (parent != null)
{
element.MaxHeight =
parent.ActualHeight;
element.MaxWidth =
parent.ActualWidth;
}
}
protected override Size ArrangeOverride(Size finalSize)
{
base.ArrangeOverride(finalSize);
double desireWidth =
AdornedElement.DesiredSize.Width;
double desireHeight =
AdornedElement.DesiredSize.Height;
double adornerWidth = this.DesiredSize.Width;
double adornerHeight = this.DesiredSize.Height;
//arranging thumbs
topLeft.Arrange(new Rect(-adornerWidth / 2,
-adornerHeight / 2, adornerWidth, adornerHeight));
topRight.Arrange(new Rect(desireWidth - adornerWidth /
2, -adornerHeight / 2, adornerWidth, adornerHeight));
bottomLeft.Arrange(new Rect(-adornerWidth / 2,
desireHeight - adornerHeight / 2, adornerWidth, adornerHeight));
bottomRight.Arrange(new Rect(desireWidth - adornerWidth /
2, desireHeight - adornerHeight / 2, adornerWidth, adornerHeight));
return finalSize;
}
protected override int VisualChildrenCount { get { return visualChilderns.Count; } }
protected override Visual GetVisualChild(int index) { return visualChilderns[index]; }
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
}
}
MainWindow code -
public partial class MainWindow : Window
{
bool isDown, isDragging,
isSelected;
UIElement selectedElement = null;
double originalLeft, originalTop;
Point startPoint;
AdornerLayer adornerLayer;
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
//registering mouse
events
this.MouseLeftButtonDown +=
MainWindow_MouseLeftButtonDown;
this.MouseLeftButtonUp +=
MainWindow_MouseLeftButtonUp;
this.MouseMove +=
MainWindow_MouseMove;
this.MouseLeave +=
MainWindow_MouseLeave;
myCanvas.PreviewMouseLeftButtonDown +=
MyCanvas_PreviewMouseLeftButtonDown;
myCanvas.PreviewMouseLeftButtonUp += MyCanvas_PreviewMouseLeftButtonUp;
}
private void StopDragging()
{
if (isDown)
{
isDown = isDragging = false;
}
}
private void
MyCanvas_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
StopDragging();
e.Handled = true;
}
private void
MyCanvas_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
//removing selected
element
if (isSelected)
{
isSelected = false;
if
(selectedElement != null)
{
adornerLayer.Remove(adornerLayer.GetAdorners(selectedElement)[0]);
selectedElement = null;
}
}
// select element if
any element is clicked other then canvas
if (e.Source != myCanvas)
{
isDown = true;
startPoint =
e.GetPosition(myCanvas);
selectedElement = e.Source as UIElement;
originalLeft = Canvas.GetLeft(selectedElement);
originalTop = Canvas.GetTop(selectedElement);
//adding adorner on selected element
adornerLayer = AdornerLayer.GetAdornerLayer(selectedElement);
adornerLayer.Add(new BorderAdorner(selectedElement));
isSelected = true;
e.Handled = true;
}
}
private void MainWindow_MouseMove(object sender, MouseEventArgs e)
{
//handling mouse
move event and setting canvas top and left value based on mouse movement
if (isDown)
{
if
((!isDragging) &&
((Math.Abs(e.GetPosition(myCanvas).X
- startPoint.X) > SystemParameters.MinimumHorizontalDragDistance)
||
(Math.Abs(e.GetPosition(myCanvas).Y - startPoint.Y) > SystemParameters.MinimumVerticalDragDistance)))
isDragging = true;
if
(isDragging)
{
Point position = Mouse.GetPosition(myCanvas);
Canvas.SetTop(selectedElement, position.Y - (startPoint.Y -
originalTop));
Canvas.SetLeft(selectedElement, position.X - (startPoint.X -
originalLeft));
}
}
}
private void MainWindow_MouseLeave(object sender, MouseEventArgs e)
{
//stop dragging on
mouse leave
StopDragging();
e.Handled = true;
}
private void
MainWindow_MouseLeftButtonUp(object
sender, MouseButtonEventArgs e)
{
//stop dragging on
mouse left button up
StopDragging();
e.Handled = true;
}
private void
MainWindow_MouseLeftButtonDown(object
sender, MouseButtonEventArgs e)
{
//remove selected
element on mouse down
if (isSelected)
{
isSelected = false;
if
(selectedElement != null)
{
adornerLayer.Remove(adornerLayer.GetAdorners(selectedElement)[0]);
selectedElement = null;
}
}
}
}
Output –
As you can see in above example, two controls Button and Textbox
added to Canvas. When user click on any control, the red corners will appear to
control. This red corners are added as an adorner to UIElement or control like
button or textbox etc. Now you can resize UIElement via resizing red corners.
You can move UIElement via Mouse Drag. So this way you can add additional
functionality to UIElement visually via adorner.
To create custom adorner, you need to create class deriving
from Adorner class. Thumb is special class derived from FrameworkElement. Thumb
class can be used to draw rectangle or other shape for Adorner. In above
example you can see Red rectangle at each corner of selected UIElement, is
created using Thumb class. Thumb class has event called DragDelta. You need to
handle DragDelta event for each thumb you create. Once you create Thumbs for
adorner you need to add all Thumbs to VisualCollection.
In MainWondow file, you need to handle different window mouse
events like MouseMove, MouseLeave, MouseLeftButtonUp, MouseLeftButtonDown etc
to handle drag/drop and mouse move event of selected element. Also you need write
code to select UIElement and apply adorner to selected element. This is handled
in MyCanvas_PreviewMouseLeftButtonDown event of Canvas in above example.
I hope this article helps you to understand more about
Adorners. Please give your feedback in comments below.
References –
See Also –
Brilliant
ReplyDeleteAwesome fella - thank you. Just what I was after.
ReplyDeletebut i want to remove the adorner layer of controls, also i tried it but process failed.
ReplyDeleteanyone help me.
i have another question for the same that when i rotate any controls on canvas , controls rotates successfully but adorner layer is unable to rotate according to controls.
ReplyDelete