强制初始化HwndHost

5
在我的WPF应用程序中,我使用HwndHost托管Win32内容。但是,创建HwndHost并不会创建本地窗口。相反,这是在稍后由WPF调用的重写的BuildWindowCore()方法中完成的。
我的托管内容需要本地窗口的窗口句柄进行自身初始化。不幸的是,我无法强制创建窗口(即让WPF调用BuildWindowCore),因此我有第二个线程,它会轮询HwndHost,直到它被初始化为止。
在.NET 4.0 / WPF 4.0中,添加了一个新方法WindowInteropHelper.EnsureHandle()。我希望它能解决这种情况,但它仅适用于Window,而不适用于HwndHost(它不从Window派生)。您有什么建议,我该怎么办?
编辑:
我忘记添加一些可能解决方案的限制:
1. HwndHost放置在一个控件中,根据用户设置,它可以是应用程序主窗口的子项,也可以通过第三方停靠管理器放置在新的Window中。这意味着在创建窗口时,我不能确定父窗口(因此也就不知道它的hWnd)是什么。
2. 虽然本地代码需要在其初始化期间使用hWnd,但仅当用户请求显示窗口时才会显示该窗口(即它最初是不可见的)。如果可能的话,应避免需要显示窗口,然后立即将其隐藏。
4个回答

3

似乎没有完美的解决方案。与提问时相比,我稍微改变了我的方法:

在我的HwndHost派生类的构造函数中,我将(可能的)父hWnd作为参数之一。然后,我使用本机CreateWindow()方法创建本机窗口,并使用给定的父hWnd。我将创建的hWnd存储在一个单独的属性中,在所有地方都使用它,而不是使用HwndHost的Handle属性。这样,我就不需要显示窗口(这解决了约束#2)。

在重写的BuildWindowCore()方法中,我将给定的父hWnd与构造函数中给出的父hWnd进行比较。如果它们不同,我将使用本机SetParent()方法重新父化我的托管窗口(这解决了约束#1)。请注意,这依赖于没有人存储父hWnd!

在代码中,相关部分(省略检查):

public class Win32Window : HwndHost
{
    public Win32Window(IntPtr parentHwnd)
    {
        this.ParentHwnd = parentHwnd;
        this.Win32Handle = NativeMethods.CreateWindowEx( /* parameters omitted*/ );
    }

    public IntPtr Win32Handle { get; private set; }
    private IntPtr ParentHwnd { get; set; }

    protected override HandleRef BuildWindowCore(HandleRef hwndParent)
    {
        if (hwndParent.Handle != this.ParentHwnd)
        {
            NativeMethods.SetParent(this.Win32Handle, hwndParent.Handle);
        }

        return new HandleRef(this, this.Win32Handle);
    }
}

2

我有一个类似的情况,我通过以下步骤解决了它:

1)创建一个派生自HwndHost的类,并将一个矩形作为构造函数参数(稍后用于BuildWindowCore):

public class MyHwndHost : HwndHost
{
    public MyHwndHost(Rect boundingBox)
    {
        BoundingBox = boundingBox;
    } 
}

2) 创建一个带有子Border元素的WPF窗口:

<Window Loaded="Window_Loaded">
    <Border Name="HostElement" />
</Window>

3)在Window_Loaded处理程序中创建HwndHost实例并将其添加到窗口中:

void Window_Loaded(object sender, RoutedEventArgs e)
{
    MyHwndHost host = new MyHwndHost(LayoutInformation.GetLayoutSlot(HostElement));
    HostElement.Child = host;
}

4) 在 Window_Loaded 处理程序中,通过 P/Invoke 或 C++/CLI 将 HWND 传递给您的本地类的初始化。我已经设置了我的本地类使用该 HWND 作为其父级,并且它创建了自己的 HWND 作为子级。


有两个问题:1)我不知道父 hWnd,因为控件稍后由第三方停靠管理器定位,并且存储的用户设置确定它是显示为自己还是作为主窗口的“子级”。2)具有 HwndHost 的控件最初可能根本不会显示(再次取决于存储的用户设置),但在启动时,传统代码需要 hWnd。 - Daniel Rose
你应该能够钩入控件的Loaded事件并在那里进行所有初始化:http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.loaded.aspx。如果旧代码需要hwnd,那么你只需要等待hwnd准备好再执行旧代码(这就是我所做的)。 - 17 of 26
从链接中引用:“当元素布局、渲染并准备好交互时发生。”如果我不显示控件,Loaded事件就不会触发。 - Daniel Rose

1
我在继承自HwndHost的类中添加了事件OnHandleCreated,该类包含句柄IntPtr。此事件在BuildWindowCore()内部调用。
因此,它归结为: public class Win32WindowHost:HwndHost { ... }
var host = new Win32WindowHost();
host.OnHandleCreated += ( sender, e ) =>
{
    var handle = e.Handle;
    // Do stuff.
};

非常有效。


很不幸,这在我的情况下不起作用。只有在HwndHost显示时才会调用BuildWindowCore()。没有其他方法可以强制创建本机hwnd(即强制WPF调用BuildWindowCore())。只有派生自Window的内容才能通过WindowInteropHelper.EnsureHandle()实现。 - Daniel Rose

1
有点晚了,但你试过在托管控件上调用 UpdateLayout() 吗?这对我有效。

不幸的是,这只在托管控件可见时有效。这正是EnsureHandle()解决的问题,但对于HwndHosts不存在。 - Daniel Rose

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接