Create WPF Window as MDI child of Win32 MDI host window
Asked Answered
R

1

6

I have a native Win32 MDI application. That application calls unmanaged code that creates WPF Window and calls User32.SetParent() to wrap it into the Win32 MDI window. That works fine, but WPF Window doesn't behave as MDI - for example activating WPF Window makes Win32 Window a background window.

My question - is it possible to convert/flag WPF Window to behave as MDI Window? I know that it should be possible to create MDI child under native code and then wrap WPF control inside, but I don't want to do that.

Rossuck answered 28/4, 2015 at 13:40 Comment(4)
What you don't want to do is what you need to do. Create the MDI child using native code and put the WPF window in the MDI child.Fur
It will be enough do SetParent for WPF window handle, but, as I remember, there are many problems with WPF window drawing.Byars
Can't you simply use a normal Windows Forms MDI window with a WPF host control inside it? Also, I am quite fascinated by the fact that it is unmanaged code that creates your WPF window. Since WPF is a managed framework, the unmanaged code is likely to have to call managed code. Can't you call that managed code directly?Linchpin
I have resolved my problem by creating MDI child using native code and passing window loop events to managed code. That events give me full control to the created window - it is possible to wrap WPF / WinForm control, handle resizing and so on. Of course I had some visual artifacts, so I had to set rendering mode to SoftwareOnly .Rossuck
I
0

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.

Illusion answered 25/3 at 7:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.