Today I'm going to tell you a WPF story. But before I start to dig deeper let me first introduce you the main characters in this post. Here they are:
There are a bunch of WPF adorners samples out there (including the SDK ones as well). But as usual when you try to create something more complex the standard samples break down. For example let say that we want to achieve something like the mini toolbar in Word 2007:
Let's start our quest with a TextBox that look like this:
and you want to put a simple button next to it that will let say will open a hyperlink that is somehow related to the TextBox. You could easily achieve this with adorners and end up with something which look like this:
How to do this? You could create something like TextBoxAdorner that will derive from Adorner and will look like this:
public class TextBoxAdorner : Adorner
{
private readonly UIElement adorningElement;
public TextBoxAdorner(TextBox textBox, UIElement adorningElement ) : base( textBox )
{
this.adorningElement = adorningElement;
if (adorningElement != null)
{
AddVisualChild(adorningElement);
}
}
protected override int VisualChildrenCount
{
get { return adorningElement == null ? 0 : 1; }
}
protected override Size ArrangeOverride(Size finalSize)
{
if (adorningElement != null)
{
Point adorningPoint = new Point();
//position at the end of the text box
adorningPoint.X = ((TextBox)AdornedElement).ActualWidth;
//position above the text box
adorningPoint.Y -= adorningElement.DesiredSize.Height;
adorningElement.Arrange( new Rect( adorningPoint, adorningElement.DesiredSize ) );
}
return finalSize;
}
protected override Visual GetVisualChild(int index)
{
if (index == 0 && adorningElement != null)
{
return adorningElement;
}
return base.GetVisualChild(index);
}
}
Note that we call AddVisualChild() method so that the element that is passed to the constructor will be visible. It is also necessary VisualChildrenCount to return count of 1 and GetVisualChild() to return the UIElement that is added to the visual tree - in our case this is the adorningElement.
So far so good. Now imagine that the button that is added as adorner did not have a click handler, but have a command set. Let's name the command - "NavigateToLink". The command handler is added to TextBox's input bindings, because the TextBox itself "knows" how to execute "NavigateToLink" command. (The sample is with TextBox, but you could imagine have your own custom control that have that logic in it). But what happens when we do this:
As you can see the button is disabled, because the command infrastructure in WPF did not manage to find a handler for our "NavigateToLink" command. This causes the Button to become disabled. Now what?
If you refer to the command readings in the links that were in the beginning of the post you will find our that the command mechanism in WPF uses the logical tree of controls to find handlers for particular commands. This bring us to point where we added the Button to visual tree. In our case we will have to add it as a logical child of the TextBox as well. This will automatically add it the logical tree of controls and the command handler will be correctly resolved and the Button enabled. Here is the new constructor of TextBoxAdorner class:
public TextBoxAdorner(TextBox textBox, UIElement adorningElement ) : base( textBox )
{
this.adorningElement = adorningElement;
if (adorningElement != null)
{
AddVisualChild(adorningElement);
textBox.AddLogicalChild( adorningElement );
}
}
Here comes the other problem. For encapsulation reasons the AddLogicalChild() method of FrameworkElement is marked as protected internal and can not be called as we call it in the snippet above. One way to workaround this is to derive from TextBox and make the method internal for your assembly. The other way is to use reflection and call the method. You can even create an extension method that will be available for all FrameworkElements. To make it even more extreme you can use expression trees to cache the method call and optimize the reflection. Here is the code:
internal static class FrameworkElementExtensions
{
private static readonly Action<FrameworkElement, object> AddLogicalChildMethod = CreateAddLogicalChildMethod();
private static Action<FrameworkElement, object> CreateAddLogicalChildMethod()
{
ParameterExpression instance = Expression.Parameter( typeof ( FrameworkElement ), "element" );
ParameterExpression parameter = Expression.Parameter( typeof ( object ), "child" );
MethodInfo methodInfo = typeof ( FrameworkElement ).GetMethod(
"AddLogicalChild", BindingFlags.NonPublic | BindingFlags.Instance );
MethodCallExpression method = Expression.Call( instance, methodInfo, parameter );
Expression<Action<FrameworkElement, object>> lambda =
Expression.Lambda<Action<FrameworkElement, object>>( method, instance, parameter );
return lambda.Compile();
}
internal static void AddLogicalChild( this FrameworkElement element, object child )
{
AddLogicalChildMethod( element, child );
}
}
You can find a sample solution here. Go play with it.
That its all for now folks. Next time I will show you how to create a generic adorner that can be used to decorate elements multiple times at different positions.