Search code examples
c#wpfgdiwindowsformshost

Get WPF window from Windows Forms control in WindowsFormsHost


I have a Windows Forms control that is hosted in a WindowsFormsHost. This is the XAML I use to accomplish this:

<Window x:Class="Forms.Address.MyWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Forms.Address"
        xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"  
        mc:Ignorable="d"
        Title="New Window" Height="500" Width="720">
    <Grid>
        <WindowsFormsHost x:Name="host">
            <local:MyFormsControl x:Name="genericName"/>
        </WindowsFormsHost>
    </Grid>
</Window>

I want to listen to events from the window that the WindowsFormsHost is in. This is simple in a Windows form because I can use the FindForm method to get the form that my control is in. However, for obvious reasons, FindForm does not work when a control is inside of a WindowsFormsHost. The parent of my control is a System.Windows.Forms.Integration.WinFormsAdapter and its parent is null.

My question is this: how can find the window that contains my control?


Solution

  • My thanks to elgonzo who suggested I use reflection to get at a field from the WinFormsAdapter class. Here is how I found the Window:

    public static Window findParentWindow(Control control) {
        WindowsFormsHost host = findWPFHost(control);
        return Window.GetWindow(host);
    }//FindParentWindow
    
    private static WindowsFormsHost findWPFHost(Control control) {
        if (control.Parent != null)
            return findWPFHost(control.Parent);
        else {
            string typeName = "System.Windows.Forms.Integration.WinFormsAdapter";
            if (control.GetType().FullName == typeName) {
                Assembly adapterAssembly = control.GetType().Assembly;
                Type winFormsAdapterType = adapterAssembly.GetType(typeName);
                return (WindowsFormsHost)winFormsAdapterType.InvokeMember("_host", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance, null, control, new string[0], null);
    
            } else
                throw new Exception("The top parent of a control within a host should be a System.Windows.Forms.Integration.WinFormsAdapter, but that is not what was found.  Someone should propably check this out.");
        }//if
    }//FindWPFHost
    

    What I did was to first recursively find the WinFormsAdapter, then use reflection to extract the _host field from it. This is the WPF WindowsFormsHost object, so its window can be found using Window.GetWindow(host).

    One caveat is that if the WindowsFormsHost is placed in a Windows Form using a ElementHost, GetWindow will return null as there is no Window.