9
votes

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

3
I should add that I think MoveWindow causes the "clipping". Before that everything's fine, but the WPF obviously has the wrong position and size.jonsb
Wouldn't it be easier to use Silverlight ?TeaDrivenDev
I don't see where you are setting the size of the parent window, and it's clearly too small. You need to size the parent window so that once the non-client area is subtracted off, the client area is the size you need for your WPF app.John Knoeller
John Knoeller: I've tried different startup sizes for the form, including maximized, but it doesn't make a difference. The WPF stuff will always be painted outside. (I understand that the small size of the form above can be misleading)jonsb
GCATNM: No, this is a full-blown .NET app, but the customer wants it inside the browser. I've tried hosting it inside an ActiveX the normal way, but the IE process grows too big and crashes (and Microsoft doesn't recommend it). XBAP could be a long-term solution, but I also need a quick short-term solution that doesn't require much rewriting.jonsb

3 Answers

6
votes

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.

3
votes

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)
0
votes

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.