wpfwindowssetparent

Hosting WPF app inside non-WPF app using WIn32 SetParent()


I have a WPF application that I want to look like it is hosted inside another - non-WPF - application. In real life this non-WPF app is an ActiveX inside Internet Explorer, but for the sake of illustrating the problem I use a simple Windows Forms app.

I use the Windows API function SetParent, which there are dozens of threads on already. However, I cannot find anything written on my exact problem: A small region on the right and bottom of the WPF app is not painted inside the non-WPF app's window.

The WPF window running by itself:
alt text

The WPF window with WinForm app's window as parent:
alt text

I don't experience the problem if a swap the WPF app with a WinForms app or a plain Win32 app (like Notepad).

The WinForm code looks like this:

private void Form1_Load(object sender, EventArgs e)
    {
        // Start process
        var psi = new ProcessStartInfo("c:\\wpfapp\\wpfapp\\bin\\Debug\\wpfapp.exe");
        psi.WindowStyle = ProcessWindowStyle.Normal;
        _process = Process.Start(psi);

        // Sleep until new process is ready
        Thread.Sleep(1000);

        // Set new process's parent to this window
        SetParent(_process.MainWindowHandle, this.Handle);

        // Remove WS_POPUP and add WS_CHILD window style to child window
        const int GWL_STYLE = -16;
        const long WS_POPUP = 0x80000000;
        const long WS_CHILD = 0x40000000;
        long style = GetWindowLong(_process.MainWindowHandle, GWL_STYLE);
        style = (style & ~(WS_POPUP)) | WS_CHILD;
        SetWindowLong(_process.MainWindowHandle, GWL_STYLE, style);

        // Move and resize child window to fit into parent's
        MoveWindow(_process.MainWindowHandle, 0, 0, this.Width, this.Height, true);
    }

Note: I'm aware that this use of SetParent is not necessarily a recommended practice, but I want to and need to find out how to do it this way, so please let me :)


Solution

  • I found a workaround that works fairly well: Call MoveWindow before SetParent:

    private void Form1_Load(object sender, EventArgs e)
    {
         // Start process            
         var psi = new ProcessStartInfo("C:\\WpfApp\\WpfApp.exe");
         psi.WindowStyle = ProcessWindowStyle.Normal;
         _process = Process.Start(psi);
    
         // Sleep until new process is ready
         Thread.Sleep(3000);
    
         // Move and resize child window to fit into parent's
         Rectangle rect;
         GetClientRect(this.Handle, out rect);
         MoveWindow(_process.MainWindowHandle, 0, 0, rect.Right - rect.Left, rect.Bottom - rect.Top, true);
    
         // Set new process's parent to this window
         SetParent(_process.MainWindowHandle, this.Handle);
    
         // Remove WS_POPUP and add WS_CHILD window style to child window
         long style = GetWindowLong(_process.MainWindowHandle, GWL_STYLE);
         style = (style & ~(WS_POPUP) & ~(WS_CAPTION)) & ~(WS_THICKFRAME) | WS_CHILD;
         SetWindowLong(_process.MainWindowHandle, GWL_STYLE, style);
    }           
    

    In the SizeChanged event handler, I take the child out of the parent, call MoveWindow and move it back into the parent. To reduce flickering, I hide the window while doing these operations:

    private void Form1_SizeChanged(object sender, EventArgs e)
    {
         ShowWindow(_process.MainWindowHandle, SW_HIDE);
    
         var ptr = new IntPtr();
         SetParent(_process.MainWindowHandle, ptr);
    
         Rectangle rect;
         GetClientRect(this.Handle, out rect);
    
         MoveWindow(_process.MainWindowHandle, 0, 0, rect.Right - rect.Left, rect.Bottom - rect.Top, true);            
         SetParent(_process.MainWindowHandle, this.Handle);
         ShowWindow(_process.MainWindowHandle, SW_SHOW);
    }
    

    it doesn't explain why MoveWindow doesn't work after SetParent, but it solves my problem, so I will mark this as the answer unless something better comes up.