Visual Level Programming vs Logical Level Programming

|

Josh Smith recently posts a great article on codeproject on how to write a custom control to enable spell checking suggestions in WPF, and his article is well written and pretty concise indeed, but he's got a problem when doing adorner layer programming, as he asked in this MSDN forum post. Since I also got the similar problem. So I digged in and with the great help of Ian Griffiths, I got the problem sorted out.  

The problem both Josh and I encounter is something to do with DP value inheritance on those visuals drawn in the adorner layer using visual layer programming technique, first off I don't agree with Josh's comments about the visual tree and adorner layer. Actually those visuals drawn in the adorner layer are also part of the visual tree if you programme it in the proper way, the reason the inherited DP values are "invisible" to the adorning visuals should be something to do with how we add those visuals in the adorner layer as Ian Griffiths suggests. The following code is a simplified version of the custom Adorner which can host a single UIElement properly:

public class UIElementAdorner : Adorner
{
    private UIElement child;
    public UIElementAdorner(UIElement element, UIElement adornedElement)
        : base(adornedElement)
    {
        this.child = element;
        base.AddLogicalChild(element);
        base.AddVisualChild(element);
    }

    protected override Size MeasureOverride(Size constraint)
    {
        this.child.Measure(constraint);
        return child.DesiredSize;
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        this.child.Arrange(new Rect(finalSize));
        return finalSize;
    }

    protected override Int32 VisualChildrenCount
    {
        get { return 1; }
    }

    protected override Visual GetVisualChild(Int32 index)
    {
        return this.child;
    }

    protected override IEnumerator LogicalChildren
    {
        get
        {
            ArrayList list = new ArrayList();
            list.Add(this.child);
            return (IEnumerator)list.GetEnumerator();
        }
    }
}

The above code demonstrates both on how to define visual tree using visual layer programming and how to define the logical tree. When defining the visual tree, you should override GetVisualChild method and VisualChildrenCount property to enable proper indexing on the hosted visuals, optionally you can make the call to Visual.AddVisualChild method to establish the child-parent relationship for the visual tree of the custom element. this call is mandatory if you are hosting single visual, and you want to enable dependency property inheritance and UI interaction on this visual. If you are hosting multiple visuals, you can use the VisualCollection to establish the children-parent relationship. When defining the logical tree, you should override the LogicalChilden property, and if you are adding single element, you should call the FrameworkElement.AddLogicalChild method, and  if you are adding multiple elements, you can use UIElementCollection to store the logical children.

So If we use UIElementCollection, the above code can be re-written as follows:

public class UIElementAdorner : Adorner
{
    private UIElementCollection children;
    public UIElementAdorner(UIElement element, UIElement adornedElement) : base(adornedElement)
    {
        children = new UIElementCollection(this, this);
        children.Add(element);
    }

    protected override Size MeasureOverride(Size constraint)
    {
        this.children[0].Measure(constraint);
        return this.children[0].DesiredSize;
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        this.children[0].Arrange(new Rect(finalSize));
        return finalSize;
    }

    protected override Int32 VisualChildrenCount
    {
        get { return 1; }
    }

    protected override Visual GetVisualChild(Int32 index)
    {
        return this.children[0];
    }

    protected override IEnumerator LogicalChildren
    {
        get
        {
            return (IEnumerator)children.GetEnumerator();
        }
    }
}

If you use VisualCollection instead, the code can be mutated like this:

public class UIElementAdorner : Adorner
{
    private UIElement child;
    private VisualCollection visuals;
    public UIElementAdorner(UIElement element, UIElement adornedElement)
        : base(adornedElement)
    {
        visuals = new VisualCollection(this);
        this.child = element;
        visuals.Add(element);
        base.AddLogicalChild(element);
        //base.AddVisualChild(element);
    }

    protected override Size MeasureOverride(Size constraint)
    {
        this.child.Measure(constraint);
        return child.DesiredSize;
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        this.child.Arrange(new Rect(finalSize));
        return finalSize;
    }

    protected override Int32 VisualChildrenCount
    {
        get { return 1; }
    }

    protected override Visual GetVisualChild(Int32 index)
    {
        return this.child;
    }

    protected override IEnumerator LogicalChildren
    {
        get
        {
            ArrayList list = new ArrayList();
            list.Add(this.child);
            return (IEnumerator)list.GetEnumerator();
        }
    }
}

As the final words, since UIElementCollection enables both visual tree level and logical tree level programming, It's more appropriate when defining new content model for you cutsom control, because ContentControl, ItemsControl, HeaderedContentControl, and HeaderedItemsControl all don't fit into your peculiar need. But if you want to programme at the visual level to gain better performance, VisualCollection is more appropriate. And also note that if you just hosing single element, VisualCollection and UIElementCollection all seem a bit overkilled, just mind you, a collection object which contains a single object takes much more memory than the single object itself, so when hosting single element, the first code sample makes more sense.

Last but not least, my greatest thanks is given to Ian Griffiths for his wonderful tips on Visual.AddVisualChild method and overriding LogicalChildren property.

0 comments: