How To Host Top-Level HWNDs In WPF

|

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>

2 comments:

Jay Int said...

I'm not sure if I'm missing something in the problem/solution- but I found that if I just set that IsTopLevel property of the child Form to False that my problems using WindowsFormHost went away.

Unknown said...

I solved the problem setting the hosted form TopLevel Property to false.

System.Windows.Forms.Integration.WindowsFormsHost host = new System.Windows.Forms.Integration.WindowsFormsHost();

Form1 contenuto = new Form1();

contenuto.TopLevel = false;

host.Child = contenuto;