我有一个本机 Win32 MDI 应用程序。该应用程序调用创建 WPF 窗口的非托管代码,并调用 User32.SetParent() 将其包装到 Win32 MDI 窗口中。这工作正常,但 WPF 窗口的行为与 MDI 不同 - 例如激活 WPF 窗口会使 Win32 窗口成为背景窗口。
我的问题 - 是否可以将 WPF 窗口转换/标记为 MDI 窗口?我知道应该可以在本机代码下创建 MDI 子项,然后将 WPF 控件包装在里面,但我不想这样做。
TL;DR: 接近起来并不难,但在这里使用 WPF 并不实用。最佳实践是使用 WinForms 作为 MDI 子窗口,并使用
ElementHost
托管 WPF 内容。
更长的版本: 你可以通过
HwndSource
获得 75%:
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();
这会起作用,但会失败:
我认为加速键和选项卡导航也存在问题,可以通过
IKeyboardInputSink
解决,但我还没有审查这些问题。
大多数问题都可以通过调用
DefMDIChildProc
根据文档来解决,但 WPF 是硬编码来调用 DefWindowProcW
。我找不到公开的 API 来影响它或拦截调用。 HwndSource
钩子 run before 处理诸如调整大小和移动之类的事情。
如果我们不顾一切,我们可以使用反射来调用HwndWrapper.AddHookLast
来拦截对
DefWindowProcW
的调用并替换
DefMDIChildProc
:
var winWPFContent = new HwndSource(classStyle: 8, style: 0x56cc0000, exStyle: 0x00050140,
x: 100, y: 100, width: 600, height: 400, "Example title", hwnd);
winWPFContent.AddHookLast(MdiChildHook);
winWPFContent.RootVisual = new UserControl1();
internal static class HwndSourceDirtyHackyExtensions
{
public static void 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 });
}
}
这在最大化/恢复和选项卡导航方面仍然存在问题,但在其他方面似乎表现正常。不过,你必须比我更乐观才能将其投入生产。使用winform。