Hosting external exe in WPF

4.1k views Asked by At

Hi I am trying to host an exe(notepad) in WPF app. Please find the code here:

public partial class MainWindow : Window
{
    private Process _process;
    private System.Windows.Forms.Panel _panel;

    [DllImport("user32.dll")]
    private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern int GetWindowLong(IntPtr hWnd, int nIndex);

    [DllImport("user32")]
    private static extern IntPtr SetParent(IntPtr hWnd, IntPtr hWndParent);

    [DllImport("user32")]
    private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);

    private const int SWP_NOZORDER = 0x0004;
    private const int SWP_NOACTIVATE = 0x0010;
    private const int GWL_STYLE = -16;
    private const int WS_CAPTION = 0x00C00000;
    private const int WS_THICKFRAME = 0x00040000;
    const string patran = "patran";
    public MainWindow()
    {
        InitializeComponent();
        _process = Process.Start("notepad.exe");

        _panel = new System.Windows.Forms.Panel();
        wfHost.Child = _panel;
        var patranPanelHandle = _panel.Handle;
        SetParent(_process.MainWindowHandle, patranPanelHandle);

        // remove control box
        int style = GetWindowLong(_process.MainWindowHandle, GWL_STYLE);
        style = style & ~WS_CAPTION & ~WS_THICKFRAME;
        SetWindowLong(_process.MainWindowHandle, GWL_STYLE, style);
        // resize embedded application & refresh
        ResizeEmbeddedApp();
    }

    private void ResizeEmbeddedApp()
    {
        if (_process == null)
            return;
        SetWindowPos(_process.MainWindowHandle, IntPtr.Zero, 0, 0, (int)_panel.Width, (int)_panel.Height, SWP_NOZORDER | SWP_NOACTIVATE);
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        Size size = base.MeasureOverride(availableSize);
        ResizeEmbeddedApp();
        return size;
    }
}

Same code works perfectly fine for Winforms so i used WinformsHost and then added a winforms panel and as described above. But the results are not as expected. The notepad is coming outside of WPF application and the parent child is not working proper

No answer posted for this too

1

There are 1 answers

0
Suresh On

I think, you are invoking SetParent too soon. You need to do SetParent once both Parent and Child windows are ready. So, what you could do is wait until the parent window is Loaded and then launch Child Process. However, call WaitForInputIdle on the Child Process before you invoke SetParent.

Below is the sample code I tried and it works:

public partial class MainWindow : Window
{
    private Process _process;

    [DllImport("user32.dll")]
    private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern int GetWindowLong(IntPtr hWnd, int nIndex);

    [DllImport("user32")]
    private static extern IntPtr SetParent(IntPtr hWnd, IntPtr hWndParent);

    [DllImport("user32")]
    private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);

    private const int SWP_NOZORDER = 0x0004;
    private const int SWP_NOACTIVATE = 0x0010;
    private const int GWL_STYLE = -16;
    private const int WS_CAPTION = 0x00C00000;
    private const int WS_THICKFRAME = 0x00040000;
    const string patran = "patran";
    public MainWindow()
    {
        InitializeComponent();

        Loaded += (s, e) => LaunchChildProcess();
    }

    private void LaunchChildProcess()
    {
        _process = Process.Start("notepad.exe");
        _process.WaitForInputIdle();

        var helper = new WindowInteropHelper(this);

        SetParent(_process.MainWindowHandle, helper.Handle);

        // remove control box
        int style = GetWindowLong(_process.MainWindowHandle, GWL_STYLE);
        style = style & ~WS_CAPTION & ~WS_THICKFRAME;
        SetWindowLong(_process.MainWindowHandle, GWL_STYLE, style);
        // resize embedded application & refresh
        ResizeEmbeddedApp();
    }

    private void ResizeEmbeddedApp()
    {
        if (_process == null)
            return;
        SetWindowPos(_process.MainWindowHandle, IntPtr.Zero, 0, 0, (int)ActualWidth, (int)ActualHeight, SWP_NOZORDER | SWP_NOACTIVATE);
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        Size size = base.MeasureOverride(availableSize);
        ResizeEmbeddedApp();
        return size;
    }

For more information on WaitForInputIdle, please refer to this MSDN page.

UPDATE

Attaching the result image:

enter image description here