Question

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 :)

Was it helpful?

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.

OTHER TIPS

You probably should resize your WPF window so that it fits into the client area of its new parent window:

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

[DllImport("user32.dll")]
static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);

// Move and resize child window to fit into parent's client area.
RECT rc = new RECT();
GetClientRect(this.Handle, out rc);
MoveWindow(_process.MainWindowHandle, 0, 0, rc.Right - rc.Left,
    rc.Bottom - rc.Top, true)

I used a Form / UserControl with a Panel. Then I used the Panel as the new parent and set the new position and size of the panel with SetWindowPos to the child window.

NOTE: MS recommends to use SetWindowPos after using SetWindowLong (or SetWindowLongPtr) to activate the new styles.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top