Visual Level Programming vs Logical Level Programming

| 0 comments

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.

How To Host Top-Level HWNDs In WPF

| 2 comments

Someone asks in MSDN forum on how to host top-level form in WPF, To be pedantic, the System.Windows.Window in WPF is "big" top-level hwnd indeed, and you cannot put another top-level hwnd into this hwnd, this is not a WPF limitation, this is actually a limitation imposed by Win32, but Win32 does allow you to add child hwnds, you can work around this limitation by "transforming" the top-level hwnd into a child hwnd, I've created a custom control which can do this trick:

using System;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Forms.Integration;
using System.Runtime.InteropServices;

namespace Sheva.Windows.Interop
{
    [System.Windows.Markup.ContentProperty("Child")]
    public class FormHost : HwndHost
    {
        private System.Windows.Forms.Form child;

        public event EventHandler<ChildChangedEventArgs> ChildChanged;

        public System.Windows.Forms.Form Child
        {
            get { return child; }
            set
            {
                HwndSource ps = PresentationSource.FromVisual(this) as HwndSource;
                if (ps != null && ps.Handle != IntPtr.Zero)
                    throw new InvalidOperationException("Cannot set the Child property after the layout is done.");
                System.Windows.Forms.Form oldChild = child;
                child = value;
                OnChildChanged(oldChild);
            }
        }

        public Boolean ShowCaption
        {
            get
            {
                CheckChildValidity();
                return (GetWindowStyle(Child.Handle) & WindowStyles.WS_BORDER) == WindowStyles.WS_CAPTION;
            }
            set
            {
                if (child == null)
                {
                    this.ChildChanged += delegate
                    {
                        if (value)
                        {
                            SetWindowStyle(Child.Handle, GetWindowStyle(Child.Handle) | WindowStyles.WS_CAPTION);
                        }
                        else
                        {
                            SetWindowStyle(Child.Handle, GetWindowStyle(Child.Handle) & ~WindowStyles.WS_CAPTION);
                        }
                    };
                }
                else
                {
                    if (value)
                    {
                        SetWindowStyle(Child.Handle, GetWindowStyle(Child.Handle) | WindowStyles.WS_CAPTION);
                    }
                    else
                    {
                        SetWindowStyle(Child.Handle, GetWindowStyle(Child.Handle) & ~WindowStyles.WS_CAPTION);
                    }
                }
            }
        }

        protected override HandleRef BuildWindowCore(HandleRef hwndParent)
        {
            CheckChildValidity();
            HandleRef childHwnd = new HandleRef(Child, child.Handle);
            SetWindowStyle(childHwnd.Handle, WindowStyles.WS_CHILD | GetWindowStyle(childHwnd.Handle));
            WindowsFormsHost.EnableWindowsFormsInterop();
            System.Windows.Forms.Application.EnableVisualStyles();
            SetParent(childHwnd.Handle, hwndParent.Handle);
            return childHwnd;
        }

        protected override void DestroyWindowCore(HandleRef hwnd)
        {
            child.Dispose();
        }

        protected void OnChildChanged(System.Windows.Forms.Form oldChild)
        {
            if (this.ChildChanged != null)
            {
                this.ChildChanged(this, new ChildChangedEventArgs(oldChild));
            }
        }

        private void CheckChildValidity()
        {
            if (child == null || child.Handle == IntPtr.Zero)
            {
                throw new ArgumentNullException("child form cannot be null");
            }
        }

        public static readonly Int32 GWL_STYLE = -16;
        public static readonly UInt32 WS_CHILD = 0x40000000;

        [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        internal static extern IntPtr SetParent(IntPtr hWnd, IntPtr hWndParent);
        [DllImport("user32.dll")]
        internal static extern WindowStyles GetWindowLong(IntPtr hWnd, Int32 nIndex);
        [DllImport("user32.dll")]
        internal static extern UInt32 SetWindowLong(IntPtr hWnd, Int32 nIndex, UInt32 dwNewLong);

        internal WindowStyles GetWindowStyle(IntPtr hWnd)
        {
            return (WindowStyles)GetWindowLong(hWnd, GWL_STYLE);
        }

        internal void SetWindowStyle(IntPtr hWnd, WindowStyles windowStyle)
        {
            SetWindowLong(hWnd, GWL_STYLE, (UInt32)windowStyle);
        }
    }

    [Flags]
    internal enum WindowStyles : uint
    {
        WS_OVERLAPPED = 0x00000000,
        WS_POPUP = 0x80000000,
        WS_CHILD = 0x40000000,
        WS_MINIMIZE = 0x20000000,
        WS_VISIBLE = 0x10000000,
        WS_DISABLED = 0x08000000,
        WS_CLIPSIBLINGS = 0x04000000,
        WS_CLIPCHILDREN = 0x02000000,
        WS_MAXIMIZE = 0x01000000,
        WS_BORDER = 0x00800000,
        WS_DLGFRAME = 0x00400000,
        WS_VSCROLL = 0x00200000,
        WS_HSCROLL = 0x00100000,
        WS_SYSMENU = 0x00080000,
        WS_THICKFRAME = 0x00040000,
        WS_GROUP = 0x00020000,
        WS_TABSTOP = 0x00010000,

        WS_MINIMIZEBOX = 0x00020000,
        WS_MAXIMIZEBOX = 0x00010000,

        WS_CAPTION = WS_BORDER | WS_DLGFRAME,
        WS_TILED = WS_OVERLAPPED,
        WS_ICONIC = WS_MINIMIZE,
        WS_SIZEBOX = WS_THICKFRAME,
        WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW,

        WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
        WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU,
        WS_CHILDWINDOW = WS_CHILD,
    }
}


The above code also demonstrates how to make custom HwndHost in WPF, note that in the setter of Child property, I've checked to see if the layout is already done, this is important because BuildWindowCore method will be called during the layout pass, and it's meaningless to set the Child property after BuildWindowCore is made. and also note that I make a call to WindowsFormsHost.EnableWindowsFormsInterop() method, this is also pretty important if you want the hosted Form object to properly get notified with the keyboard related messages, because Windows Forms and WPF have different way of implementing message pumping. If you want to know all the details related to how the message loop interop between Windows Forms and WPF works, you can read the WPF documentation article: Windows Forms and WPF Interoperability Input Architecture.

  The usage of FormHost in XAML is pretty straightforward as the following XAML snippet domonstrates:

<Window x:Class="TestCode.HostingFormDemo"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
  
xmlns:cc="clr-namespace:Sheva.Windows.Interop"
  
Title="Hosting Form In WPF"
    >
    <
cc:FormHost ShowCaption="False">
      <
wf:Form/>
    </
cc:FormHost>
</
Window>

Another Way To Listen To DP Value Change

| 0 comments

Ben Constable just blogs about another elegant way to listen to dependency property change, the trick here is using DependencyPropertyDescriptor, imagine that you have a Label called myLabel, and you want to get notified when it's ContentProperty is changed, then you can do something like the following:

DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(Label.ContentProperty, typeof(Label));
if (dpd != null)
{
    dpd.AddValueChanged(myLabel, delegate
    {
        // Add your logic here to respond to value change.
    });
}

The other ways in WPF that you can use to listen to property change is to subclass an existing Control, and override the metadata of a dependency property whose property change you want to listen to, when you do so, you need to specify a PropertyChangedCallback which a delegate to your property change event handler, or instead of using PropertyChangedCallback, you can override the OnPropertyChanged method, and place your logic there, as a bonus, you can get additional information things like which property is changed by examining the value of passed-in DependencyPropertyChangedEventArgs argument's Property property.