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:

  • Adorner - adorners are simple UIElement decorators. Suppose that you have a RichTextBox and you want to place a comment box over it, so that users can comment on the text in the box. This can be archived with adorners. Actually the WPF framework uses adorners internally for exactly the same purpose when you edit and annotate FlowDocument in a DocumentViewer. This is the reason why adorners are in System.Windows.Documents namespace.
  • Command - commands are UI flavored implementation of the command pattern in WPF. You can read about them here.
  • Logical Tree - ButtonThere are two control trees in WPF. One is the visual one, the other is the logical one. Imagine a Button for example. The button as logical entity act as a single control, you can click on it etc. However the visual representation of the button contains different UIElements - borders, TextBlock, etc. This means that WPF should maintain two separate control trees, one for the behavior elements - logical tree and one for the visual representation - visual tree. More info here.

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:

WordMiniToolbar 

Let's start our quest with a TextBox that look like this:

FunkyTextBox

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:

FunkyTextBoxWithLink

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:

FunkyTextBoxWithLinkDsiabled

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.


Related Posts

Comments

Comments are disabled in preview mode.