This article explains a technique that I have used to define WPF adorners in XAML. The technique requires two custom classes.
These classes are used to define:
- the control to be adorned; and
- the UI elements that make up the adorner
The main class is
AdornedControl which is derived from ContentControl. The content of
AdornedControl is displayed in the normal way in the visual tree of the UI. The adorner is defined using the
AdornerContent property of
AdornedControl. The content of this property, in XAML, defines the UI elements that will be placed in the adorner that is attached to the
I have included two sample projects to accompany this article. The simple sample shows a very simple use of
AdornedControl that is described in this article. The advanced sample shows a more advanced usage that meets the needs described in the Background section below.
It is assumed that you already know C# and have a basic knowledge of using WPF and XAML. Knowledge of the WPF visual and logical tree will also be helpful.
Recently, I found that I was in need of a way to display auxiliary controls when the user hovers the mouse over a particular control.
The particular control is a flow-chart node embedded in a Canvas that looks like this:
The purpose of the auxiliary controls was for the user to be able to drag the node around the Canvas and also to be able to remove the node from the Canvas.
This is what the node looks like with the auxiliary controls displayed:
Normally, the auxiliary controls will not be displayed. They will only be visible when the mouse has moved over the node. They will remain visible until the mouse has moved away from the node and a period of time has elapsed. I decided that adorners would fit this situation well because they are defined outside of the node's visual tree and therefore will not interfere with the normal layout and visuals of the node.
The usual way of creating adorners and adding them to the adorner layer is accomplished through procedural code. What I really wanted was to design both the adorned controls and their adorners in XAML. I created two custom classes that work together to allow this:
But first let's review the usual way of working with adorners.
Using Adorners - The Usual Way
The normal method of using adorners is procedural. You need to create a class that derives from Adorner. In the derived class, you either have some custom rendering code or you attach UI elements and visuals as children to the adorner. Next, you create an instance of your derived adorner class. The UI element that is to be adorned is passed into the constructor of the adorner. Lastly, the adorner is added to the adorner layer. All this is achieved procedurally in C# code.
To illustrate, here is a simple example.
Firstly, here is the class derived from adorner:
class MyAdorner : Adorner
public MyAdorner(UIElement adornedElement) :
Custom rendering code is added to '
OnRender' where you can use '
protected override void OnRender(DrawingContext drawingContext)
When you are ready to display the adorner and allow the user to interact with it must be added to the adorner layer.
AdornerLayer.GetAdornerLayer is called and typically passed a reference to the UI element that is to be adorned.
GetAdornerLayer searches back up the visual tree for an appropriate adorner layer to use.
Control parentControl = ...
UIElement adornedElement = ...
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(parentControl);
MyAdorner myAdorner = new MyAdorner(adornedElement);
The alternative to writing custom rendering code is to add children to the visual tree of the adorner. This means that any class derived from Visual can be instanced and added to the visual tree underneath the adorner. I have used this technique in
This has only been a brief discussion about using adorners procedurally. The main purpose of my article is to discuss how to use adorners defined in XAML.
Defining Adorners in XAML using AdornedControl
There are many advantages to defining the UI in XAML. Not only is it simpler than writing the equivalent procedural code, but it's also safer and less prone to errors. Unfortunately, as shown above, adorners are usually created procedurally and cannot, by default, be defined in XAML. This seems like an unnecessary omission from WPF and so I implemented a class that allows it.
AdornedControl allows adorners to be defined in XAML. It defines both the adorner and the control that is to be adorned.
This is the most basic definition of an
AdornedControl in XAML:
AdornedControl derives from
ContentControl. As such it can wrap any other content, visual or UI element. For example, this defines the UI element to be adorned as a rectangle:
The next example defines an ellipse as the adorner for the rectangle:
Now we use
HorizontalAdornerPlacement combined with the adorner's
HorizontalAlignment to place the adorner horizontally, and outside the adorned control on the right.
AdornedControl has various ways of showing and hiding the adorner but here we use the IsAdornerVisible property to make it visible by default:
IsAdornerVisible can also be set procedurally to show or hide the adorner. Alternatively the
Hide() functions and
Hide commands can be used. In the advanced sample, I use animation to fade the adorner in and out when it is shown and hidden.
Finally, the other custom class which I haven't yet mentioned is
FrameworkElementAdorner. This class is used internally by
AdornedControl. It derives from
Adorner and references a
FrameworkElement as its child in the visual tree. This is what allows us to add any
FrameworkElement into the visual tree of the adorner.
is based on
by Josh Smith which can be found here
. I adapted it to work with
and made a few modifications that I needed. The code for
is a good example of how to create an adorner that has visual children.
This example has explained how to use
AdornedControl to define, in XAML, an adorned control and its adorner.
- 7th February, 2010: Article updated
SizeChanged of the adorned element is now monitored in order to update the placement of the adorner
- Fixed issue with placement of the adorner on the outside top & left of the adorned element
- 27th February, 2010: Code updated
- The update is simply to remove the copyright information from Assembly.cs that was automatically added when the project was generated. I tested that the code still compiles and runs.
- 18th June, 2010: Article updated
- The '
Focusable' property for
AdornedControl is now set to '
false' by default.
- Added a new improved sample project. Functionally the 'improved sample' is the same as the 'advanced sample', however the animation code to fade in and fade out the adorner is now integrated into the
AdornedControl class itself which makes it trivial to move this functionality to new projects. The new methods '
FadeInAdorner' and '
FadeOutAdorner' can be called to fade the adorner in and out. Alternately the '
FadeIn' and '
FadeOut' commands can trigger this behaviour. The new property '
IsMouseOverShowEnabled' can be set to '
true' to automatically fade in and show the adorner when the mouse cursor is hovered over the adorned control.
- 25th September, 2010: Article updated
- The animation state of the adorner is now set when showing/hiding the adorner by setting the
IsAdornerVisible property (thanks to Tri Q Tran for pointing this out)
- Made some changes to make sure the animation state of the adorner is always correct
- Opacity is now animated on the adorner rather than the adorner content
- 11th October, 2010: Article updated
- Added a new dependency property to
AdornedTemplatePartName is used to specify the part name of an element in the visual-tree that is to be adorned. By default, the property is set to
null which causes the
AdornedControl itself to be the UI element that is adorned (the original behaviour). When the property is set to a valid part name,
AdornedControl searches the visual-tree below itself to find the named UI element that is to be adorned. For example, when
AdornerContent is an editable
PART_EditableTextBox will cause the
TextBox part of the
ComboBox to be adorned.
This feature was requested by Richard Deeming.
- 15th March, 2011: Article updated
- Fixed an issue in ImprovedAdornedControlSample. This issue was reported by Member 2477019 (see article messages for details) and the fix was proposed by Louis-Philippe Lauzier.
I removed the code in
HideAdornerInternal that was setting
false. This line of code appears to be interfering with data-binding, and after analysis, this line of code appears totally unnecessary, so removing it should cause no issues.