W WPF Adorner to element pozwalający dołączać do innych kontrolek jakieś efekty graficzne. Na przykład jeśli użytkownik wpiszę nieprawidłową wartość w pole edycyjne, wtedy Adorner może dołączyć do takiego pola ikonkę ostrzegając, że wpisano niepoprawną wartość. Adorner zatem doczepiany jest do jakieś kontrolki a rysowany jest na tzw. AdornerLayer. Spróbujmy zaimplementować adroner, który po doczepieniu do kontrolki pozwala zmienić jej rozmiar. Zacznijmy od zadeklarowania naszego adorner’a:
public class ResizingAdorner:Adorner
{
public ResizeAdorner(UIElement adornedElement) : base(adornedElement)
{
}
}
Konstruktor przyjmuje kontrolkę do której doczepiony będzie ResizeAdorner umożliwiający zmianę jej rozmiaru.
Następnie musimy dodać 4 małe kwadraciki, które później zostaną rozmieszczone w narożnikach kontrolki. WPF dostarcza klasę Thumb, która jest kontrolką dostarczającą potrzebne zdarzenia. Dzięki Thumb w łatwy sposób będziemy mogli uchwycić zdarzenie kiedy użytkownik klika na wspomniany kwadracik i przesuwa myszką w celu powiększenia lub pomniejszenia kontrolki. Przykład:
public class ResizingAdorner:Adorner
{
private readonly Thumb[] _thumbs;
private readonly VisualCollection _visualCollection;
private const float ThumbSize = 10;
public ResizeAdorner(UIElement adornedElement) : base(adornedElement)
{
_visualCollection=new VisualCollection(this);
_thumbs=new Thumb[4];
_thumbs[0] = CreateThumb();
_thumbs[1] = CreateThumb();
_thumbs[2] = CreateThumb();
_thumbs[3] = CreateThumb();
}
private Thumb CreateThumb()
{
Thumb thumb=new Thumb();
thumb.Width = thumb.Height = ThumbSize;
thumb.BorderThickness=new Thickness(1);
thumb.BorderBrush = Brushes.Black;
_visualCollection.Add(thumb);
return thumb;
}
protected override Visual GetVisualChild(int index)
{
return _visualCollection[index];
}
protected override int VisualChildrenCount
{
get { return _visualCollection.Count; }
}
}
Powyższy kod wyłącznie tworzy 4 thumb’y. VisualCollection oraz metody GetVisualChild, VisualChildrenCount powinny być znane z poprzedniego postu. Musimy je jednak rozmieścić w czterech narożnikach:
public class ResizingAdorner:Adorner
{
private readonly Thumb[] _thumbs;
private readonly VisualCollection _visualCollection;
private const float ThumbSize = 10;
public ResizeAdorner(UIElement adornedElement) : base(adornedElement)
{
_visualCollection=new VisualCollection(this);
_thumbs=new Thumb[4];
_thumbs[0] = CreateThumb();
_thumbs[1] = CreateThumb();
_thumbs[2] = CreateThumb();
_thumbs[3] = CreateThumb();
}
private Thumb CreateThumb()
{
Thumb thumb=new Thumb();
thumb.Width = thumb.Height = ThumbSize;
thumb.BorderThickness=new Thickness(1);
thumb.BorderBrush = Brushes.Black;
_visualCollection.Add(thumb);
return thumb;
}
protected override Visual GetVisualChild(int index)
{
return _visualCollection[index];
}
protected override int VisualChildrenCount
{
get { return _visualCollection.Count; }
}
protected override Size ArrangeOverride(Size finalSize)
{
double controlWidth = AdornedElement.DesiredSize.Width;
double controlHeight = AdornedElement.DesiredSize.Height;
_thumbs[0].Arrange(new Rect(-ThumbSize / 2, -ThumbSize / 2, ThumbSize, ThumbSize));
_thumbs[1].Arrange(new Rect(controlWidth - ThumbSize / 2, -ThumbSize / 2, ThumbSize, ThumbSize));
_thumbs[2].Arrange(new Rect(-ThumbSize / 2, controlHeight - ThumbSize / 2, ThumbSize, ThumbSize));
_thumbs[3].Arrange(new Rect(controlWidth - ThumbSize / 2, controlHeight - ThumbSize / 2, ThumbSize, ThumbSize));
return finalSize;
}
}
ArrangeOverride jest wywoływane gdy nastąpi potrzeba ponownego rozmieszczenia kontrolek – np. gdy rodzic zmieni rozmiar. W ciele metody pozycjonujemy 4 thumb’y do narożników. Gdyby teraz odpalić aplikację i umieścić zaimplementowany adorner na przycisku, to powinniśmy zaobserwować coś takiego:

W tej chwili thumb’y nic nie robią – po prostu osiągnęliśmy efekt wizualny. Naszym celem jest jednak umożliwienie zmiany rozmiaru poprzez kliknięcie na jednym z 4 thumbow. Sprawę znacząco uproszcza zdarzenie Thumb.DragDelta:
public class ResizingAdorner:Adorner
{
private readonly Thumb[] _thumbs;
private readonly VisualCollection _visualCollection;
private const float ThumbSize = 10;
public ResizeAdorner(UIElement adornedElement) : base(adornedElement)
{
_visualCollection=new VisualCollection(this);
_thumbs=new Thumb[4];
_thumbs[0] = CreateThumb();
_thumbs[1] = CreateThumb();
_thumbs[2] = CreateThumb();
_thumbs[3] = CreateThumb();
_thumbs[2].DragDelta += BottomLeftThumbDrag;
}
private Thumb CreateThumb()
{
Thumb thumb=new Thumb();
thumb.Width = thumb.Height = ThumbSize;
thumb.BorderThickness=new Thickness(1);
thumb.BorderBrush = Brushes.Black;
_visualCollection.Add(thumb);
return thumb;
}
protected override Visual GetVisualChild(int index)
{
return _visualCollection[index];
}
protected override int VisualChildrenCount
{
get { return _visualCollection.Count; }
}
protected override Size ArrangeOverride(Size finalSize)
{
double controlWidth = AdornedElement.DesiredSize.Width;
double controlHeight = AdornedElement.DesiredSize.Height;
_thumbs[0].Arrange(new Rect(-ThumbSize / 2, -ThumbSize / 2, ThumbSize, ThumbSize));
_thumbs[1].Arrange(new Rect(controlWidth - ThumbSize / 2, -ThumbSize / 2, ThumbSize, ThumbSize));
_thumbs[2].Arrange(new Rect(-ThumbSize / 2, controlHeight - ThumbSize / 2, ThumbSize, ThumbSize));
_thumbs[3].Arrange(new Rect(controlWidth - ThumbSize / 2, controlHeight - ThumbSize / 2, ThumbSize, ThumbSize));
return finalSize;
}
void BottomLeftThumbDrag(object sender, DragDeltaEventArgs args)
{
FrameworkElement adornedElement = (FrameworkElement)AdornedElement;
adornedElement.Width = Math.Max(0,adornedElement.Width - args.HorizontalChange);
adornedElement.Height = Math.Max(0,args.VerticalChange + adornedElement.Height);
}
}
DragDelta jest wywoływane gdy użytkownik klika na thumb i rusza myszką. Przesunięcie, które dokonał jest zapisywane we właściwości VerticalChange oraz HortizontalChange. Analogicznie należy obsłużyć pozostałe 3 thumb’y:
public class ResizingAdorner:Adorner
{
private readonly Thumb[] _thumbs;
private readonly VisualCollection _visualCollection;
private const float ThumbSize = 10;
public ResizeAdorner(UIElement adornedElement) : base(adornedElement)
{
_visualCollection=new VisualCollection(this);
_thumbs=new Thumb[4];
_thumbs[0] = CreateThumb();
_thumbs[1] = CreateThumb();
_thumbs[2] = CreateThumb();
_thumbs[3] = CreateThumb();
_thumbs[0].DragDelta += TopLeftThumbDrag;
_thumbs[1].DragDelta += TopRightThumbDrag;
_thumbs[2].DragDelta += BottomLeftThumbDrag;
_thumbs[3].DragDelta += BottomRightThumbDrag;
}
private Thumb CreateThumb()
{
Thumb thumb=new Thumb();
thumb.Width = thumb.Height = ThumbSize;
thumb.BorderThickness=new Thickness(1);
thumb.BorderBrush = Brushes.Black;
_visualCollection.Add(thumb);
return thumb;
}
protected override Visual GetVisualChild(int index)
{
return _visualCollection[index];
}
protected override int VisualChildrenCount
{
get { return _visualCollection.Count; }
}
protected override Size ArrangeOverride(Size finalSize)
{
double controlWidth = AdornedElement.DesiredSize.Width;
double controlHeight = AdornedElement.DesiredSize.Height;
_thumbs[0].Arrange(new Rect(-ThumbSize / 2, -ThumbSize / 2, ThumbSize, ThumbSize));
_thumbs[1].Arrange(new Rect(controlWidth - ThumbSize / 2, -ThumbSize / 2, ThumbSize, ThumbSize));
_thumbs[2].Arrange(new Rect(-ThumbSize / 2, controlHeight - ThumbSize / 2, ThumbSize, ThumbSize));
_thumbs[3].Arrange(new Rect(controlWidth - ThumbSize / 2, controlHeight - ThumbSize / 2, ThumbSize, ThumbSize));
return finalSize;
}
void BottomLeftThumbDrag(object sender, DragDeltaEventArgs args)
{
FrameworkElement adornedElement = (FrameworkElement)AdornedElement;
adornedElement.Width = Math.Max(0,adornedElement.Width - args.HorizontalChange);
adornedElement.Height = Math.Max(0,args.VerticalChange + adornedElement.Height);
}
void BottomRightThumbDrag(object sender, DragDeltaEventArgs args)
{
FrameworkElement adornedElement = (FrameworkElement)AdornedElement;
adornedElement.Width = Math.Max(0, adornedElement.Width + args.HorizontalChange);
adornedElement.Height = Math.Max(0, args.VerticalChange + adornedElement.Height);
}
void TopLeftThumbDrag(object sender, DragDeltaEventArgs args)
{
FrameworkElement adornedElement = (FrameworkElement)AdornedElement;
adornedElement.Width = Math.Max(0, adornedElement.Width - args.HorizontalChange);
adornedElement.Height = Math.Max(0, adornedElement.Height - args.VerticalChange);
}
void TopRightThumbDrag(object sender, DragDeltaEventArgs args)
{
FrameworkElement adornedElement = (FrameworkElement)AdornedElement;
adornedElement.Width = Math.Max(0, adornedElement.Width + args.HorizontalChange);
adornedElement.Height = Math.Max(0, adornedElement.Height - args.VerticalChange);
}
}
Kolejne udoskonalenie to zmiana kursora w zależności od tego, który thumb jest pod myszką. Ostateczna wersja gotowa do użycia wygląda następująco:
public class ResizingAdorner:Adorner
{
private readonly Thumb[] _thumbs;
private readonly VisualCollection _visualCollection;
private const float ThumbSize = 10;
public ResizingAdorner(UIElement adornedElement) : base(adornedElement)
{
_visualCollection=new VisualCollection(this);
_thumbs=new Thumb[4];
_thumbs[0] = CreateThumb(TopLeftThumbDrag,Cursors.SizeNWSE);
_thumbs[1] = CreateThumb(TopRightThumbDrag,Cursors.SizeNESW);
_thumbs[2] = CreateThumb(BottomLeftThumbDrag,Cursors.SizeNESW);
_thumbs[3] = CreateThumb(BottomRightThumbDrag,Cursors.SizeNWSE);
}
private Thumb CreateThumb(DragDeltaEventHandler dragDeltaEventHandler,Cursor cursor)
{
Thumb thumb=new Thumb();
thumb.Width = thumb.Height = ThumbSize;
thumb.BorderThickness=new Thickness(1);
thumb.BorderBrush = Brushes.Black;
thumb.DragDelta += dragDeltaEventHandler;
thumb.Cursor = cursor;
_visualCollection.Add(thumb);
return thumb;
}
protected override Visual GetVisualChild(int index)
{
return _visualCollection[index];
}
protected override int VisualChildrenCount
{
get { return _visualCollection.Count; }
}
protected override Size ArrangeOverride(Size finalSize)
{
double controlWidth = AdornedElement.DesiredSize.Width;
double controlHeight = AdornedElement.DesiredSize.Height;
_thumbs[0].Arrange(new Rect(-ThumbSize / 2, -ThumbSize / 2, ThumbSize, ThumbSize));
_thumbs[1].Arrange(new Rect(controlWidth - ThumbSize / 2, -ThumbSize / 2, ThumbSize, ThumbSize));
_thumbs[2].Arrange(new Rect(-ThumbSize / 2, controlHeight - ThumbSize / 2, ThumbSize, ThumbSize));
_thumbs[3].Arrange(new Rect(controlWidth - ThumbSize / 2, controlHeight - ThumbSize / 2, ThumbSize, ThumbSize));
return finalSize;
}
void BottomLeftThumbDrag(object sender, DragDeltaEventArgs args)
{
FrameworkElement adornedElement = (FrameworkElement)AdornedElement;
adornedElement.Width = Math.Max(0,adornedElement.Width - args.HorizontalChange);
adornedElement.Height = Math.Max(0,args.VerticalChange + adornedElement.Height);
}
void BottomRightThumbDrag(object sender, DragDeltaEventArgs args)
{
FrameworkElement adornedElement = (FrameworkElement)AdornedElement;
adornedElement.Width = Math.Max(0, adornedElement.Width + args.HorizontalChange);
adornedElement.Height = Math.Max(0, args.VerticalChange + adornedElement.Height);
}
void TopLeftThumbDrag(object sender, DragDeltaEventArgs args)
{
FrameworkElement adornedElement = (FrameworkElement)AdornedElement;
adornedElement.Width = Math.Max(0, adornedElement.Width - args.HorizontalChange);
adornedElement.Height = Math.Max(0, adornedElement.Height - args.VerticalChange);
}
void TopRightThumbDrag(object sender, DragDeltaEventArgs args)
{
FrameworkElement adornedElement = (FrameworkElement)AdornedElement;
adornedElement.Width = Math.Max(0, adornedElement.Width + args.HorizontalChange);
adornedElement.Height = Math.Max(0, adornedElement.Height - args.VerticalChange);
}
}
Na koniec przyszedł czas na umieszczenie adorner’a na kontrolce. Najpierw zadeklarujmy przycisk wewnątrz AdornerDecorator:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="160" Width="397" xmlns:my="clr-namespace:WpfApplication2">
<AdornerDecorator>
<Button Name="button" Width="100" Height="50">Hello World</Button>
</AdornerDecorator>
</Window>
AdornerDecorator umożliwi nam uzyskanie AdornerLayer, który z kolei będzie służył do rysowania adorner’a. W Code-Behind musimy umieścić zaimplementowany adorner na przycisku:
public MainWindow()
{
InitializeComponent();
AdornerLayer layer = AdornerLayer.GetAdornerLayer(button);
layer.Add(new ResizingAdorner(button));
}
Efekt końcowy:

W dość prosty sposób napisaliśmy adorner’a, który z łatwością i bez żadnych modyfikacji można stosować do wszystkich kontrolek. Implementacja jest kompletnie odizolowana od specyficznej kontrolki.