It's not hard to get annoyingly close, but it is not practical to use WPF here. The best practice is to use WinForms for the MDI Child window, and to use an ElementHost
to host the WPF content. It is possible to use WPF if you break the rules, though.
You can get 75% there with HwndSource
:
var winWPFContent = new HwndSource(classStyle: 8, style: 0x56cc0000, exStyle: 0x00050140,
x: 100, y: 100, width: 600, height: 400, "Example title", hwnd);
winWPFContent.RootVisual = new UserControl1();
This will work, but will fail to:
- Track activation (Light up and dim title bar)
- Maximize/restore properly (can get "stuck" maximized)
- Cycling through MDI windows using next/previous (will cycle to desktop windows, rather than staying in MDI)
- Resize / Move like normal MDI windows (unbounded by MDI frame)
Most of the issues can be addressed by calling DefMDIChildProc
according to the docs, but WPF is hard-coded to call DefWindowProcW
. I can't find API exposed to influence that, or to intercept the call. HwndSource
hooks run before things like resizing and moving are handled.
If we throw caution to the wind, we can use reflection to call HwndWrapper.AddHookLast
to intercept calls to DefWindowProcW
and substitute DefMDIChildProcW
:
var winWPFContent = new HwndSource(classStyle: 8, style: 0x56cc0000, exStyle: 0x00050140,
x: 100, y: 100, width: 600, height: 400, "Example title", hwnd);
var mdiHook = winWPFContent.AddHookLast(MdiChildHook);
// We have to keep the MdiChildHook delegate alive for the life of the window
// or it will stop being called. The event listener will root the delegate
// off the HwndSource.
winWPFContent.Disposed += (_, _) => GC.KeepAlive(mdiHook);
winWPFContent.RootVisual = new UserControl1();
internal static class HwndSourceDirtyHackyExtensions
{
public static Delegate AddHookLast(this HwndSource hwndSource, HwndSourceHook hook)
{
// Get HwndWrapper
var a = typeof(HwndSource).GetField("_hwndWrapper", BindingFlags.Instance | BindingFlags.NonPublic)
?? throw new InvalidOperationException("_hwndWrapper not found");
var wrapper = a.GetValue(hwndSource)
?? throw new InvalidOperationException("_hwndWrapper null");
// Call AddHookLast
var miAddHookLast = wrapper.GetType().GetMethod("AddHookLast", BindingFlags.Instance | BindingFlags.NonPublic)
?? throw new InvalidOperationException("AddHookLast not found");
var tHwndWrapperHook = miAddHookLast.GetParameters().Single().ParameterType;
var hookDelegate = Delegate.CreateDelegate(tHwndWrapperHook, hook.Method);
miAddHookLast.Invoke(wrapper, new object[] { hookDelegate });
return hookDelegate;
}
}
A full sample is available at https://github.com/mgaffigan/WpfMdiChildSample/tree/master.