Forcing initialization of a HwndHost
Asked Answered
R

4

5

In my WPF application, I host Win32 content using HwndHost. However, creating a HwndHost does not create the native window. Rather, this is done in the overridden BuildWindowCore() method which is called sometime later by WPF.

My hosted content needs the window handle of the native window for its own initialization. Unfortunately, there is no way I can force the creation of the window (i.e. having WPF call the BuildWindowCore), so I have a second thread which polls the HwndHost until it has been initialized.

In .NET 4.0 / WPF 4.0, a new method WindowInteropHelper.EnsureHandle() was added. I had hoped this would resolve the situation, but it only works for a Window, not a HwndHost (which doesn't derive from Window). Do you have a suggestion what I could do instead?

EDIT:

I forgot to add some more constraints for a possible solution:

  1. The HwndHost is placed in a control which, depending on user settings, can be a child of the application's main window or can be placed in a new Window (via a third-party docking manager). This means that during creation of the window I do not know for sure what the parent Window (and thus its hWnd) will be.
  2. While the native code needs the hWnd during its initialization, the window is only displayed when the user requests it to be shown (i.e. it is invisible at first). Needing to show the window, only to hide it immediately again, should be avoided, if possible.
Redound answered 6/10, 2010 at 9:15 Comment(0)
R
3

There seems to be no perfect solution. I changed my approach slightly compared to the time of the question being asked:

In the constructor of my HwndHost-derived class I have the (possible) parent hWnd as one of the parameters. I then create the native window using the native CreateWindow() method, using the given parent hWnd. I store the created hWnd in a separate property, which I use everywhere instead of the HwndHost's Handle property. That way, I don't need to show the window (this solves constraint #2).

In the overridden BuildWindowCore() method, I compare the given parent hWnd with the one I was given in the constructor. If they are different, I reparent my hosted window using the native SetParent() method (this solves constraint #1). Note that this relies on nobody storing the parent hWnd!

In code, the relevant parts (checks omitted):

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);
    }
}
Redound answered 24/6, 2011 at 10:42 Comment(0)
S
2

I have a similar situation and I solved it by doing the following:

1) Create an HwndHost derived class that takes a Rect as a constructor argument (later used in BuildWindowCore):

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

2) Create a WPF Window with a child Border element:

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

3) Create the HwndHost instance and add it to the window in the Window_Loaded handler:

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

4) Also in the Window_Loaded handler, pass the HWND to the initialization of your native class either through P/Invoke or C++/CLI. I have my native class set up to use that HWND as its parent and it creates its own HWND as a child.

Sweetie answered 27/5, 2011 at 14:47 Comment(3)
There are two problems: 1) I don't know the parent hWnd, since the control is later positioned by a third-party docking manager, and the stored user settings determine if it is shown on its own or as a "child" of the main window. 2) The control with the HwndHost may not be shown at all initially (depending again on the stored user settings), but at startup the legacy code needs the hWnd.Redound
You should be able to hook into the Loaded event on your control and do all initialization there: msdn.microsoft.com/en-us/library/…. If the legacy code needs the hwnd then you just need to hold off on doing anything with the legacy code until the hwnd is ready (which is what I had to do).Sweetie
Quote from the link: "Occurs when the element is laid out, rendered, and ready for interaction." If I don't show the control, Loaded won't fire.Redound
D
1

A bit late, but have you tried calling UpdateLayout() on the hosting control? That worked for me

Disentail answered 21/6, 2011 at 8:28 Comment(1)
Unfortunately, that only works if the hosting control is visible. That is precisely the problem that EnsureHandle() solves, but it doesn't exist for HwndHosts.Redound
J
1

I added the event OnHandleCreated to my HwndHost-inherited class, which contains the handle IntPtr. This event is invoked inside BuildWindowCore().

So it boils down to:

public class Win32WindowHost : HwndHost { ... }

var host = new Win32WindowHost();
host.OnHandleCreated += ( sender, e ) =>
{
    var handle = e.Handle;
    // Do stuff.
};

Works a treat.

Jospeh answered 19/2, 2014 at 11:47 Comment(1)
Unfortunately, this does not work in my case. BuildWindowCore() is only invoked when the HwndHost is shown. There is no other way to force the creation of the native hwnd (i.e. force WPF to call BuildWindowCore()). This is only possible for something derived from Window, via WindowInteropHelper.EnsureHandle().Redound

© 2022 - 2024 — McMap. All rights reserved.