Host external app in a WPF panel

0

I am trying to host/run an Exe application within a panel inside of the main window of my program. The main window has a grid with 3 columns - two columns split evenly with a vertical grid splitter to section the left and right columns. The left column has a tab control, but the right column is what I want to place the exe application inside of and essentially have the application resize based on the dock inside of the right column.

I have built the program in a similar fashion to Hosting external app in WPF window. This example is much simpler than what I"m going to use for the exe and additional functionality but the example should explain my current issue.

I can launch the exe into the window, but not within the Dockpanel, or any other control, in the right column. The exe just stays inside of the window statically where it's placed as opposed to being placed inside of the column. I can go in and change the starting location of the process window so it actually opens on top of the right column, and appears to be within that column's dock panel but again it's only static.

All of the examples I've seen are placing a process inside of the window, and not within a container. I've tried setting the WindowsInteropHelper to the dock panel but it still just sends it to the main window.

var helper = new WindowInteropHelper(GetWindow(this.ApplicationDock));

I've also tried a handful of articles including the two below but still haven't found the solution I"m looking for.

How can I run another application within a panel of my C# program?

Docking Window inside another Window

One possibility I've thought about, is upon window change update the process window handler to be placed at the correct coordinates of where the right column starts at. To me this seems like it should be the last resort though and not the correct way to handle this.

My main application

<Window x:Class="ProgramInProgram.MainWindow"
    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:ProgramInProgram"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <TabControl Grid.Column="0">
        <TabItem Header="Blue Tab">
            <Grid Background="Blue"></Grid>
        </TabItem>
        <TabItem Header="Red Tab" >
            <Grid Background="Red"></Grid>
        </TabItem>
    </TabControl>
    <GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
    <DockPanel Grid.Column="2" x:Name="ApplicationDock">
    </DockPanel>
</Grid>
</Window>

The Code behind

public partial class MainWindow : Window
{

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

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

    [DllImport("user32.dll")]
    private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);

    [DllImport("user32.dll")]
    private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, int uFlags);

    private Process process;

    private const int SWP_ZOZORDER = 0x0004;
    private const int SWP_NOACTIVATE = 0x0010;
    private const int GWL_STYLE = (-16);
    private const int WS_CAPTION = 0x00C00000;
    private const int WS_VISIBLE = 0x10000000;
    private const int WS_THICKFRAME = 0x00040000;
    const string patran = "patran";

    public MainWindow()
    {
        InitializeComponent();

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

    private void LaunchChildProcess()
    {
        this.process = Process.Start("Notepad.exe");
        this.process.WaitForInputIdle();

        var helper = new WindowInteropHelper(GetWindow(this.ApplicationDock));

        SetParent(this.process.MainWindowHandle, helper.Handle);

        int style = GetWindowLong(this.process.MainWindowHandle, GWL_STYLE);
        style = style & ~WS_CAPTION & ~WS_THICKFRAME;
        SetWindowLong(this.process.MainWindowHandle, GWL_STYLE, style);
        ResizeEmbeddedApp();
    }

    private void ResizeEmbeddedApp()
    {
        if (this.process == null)
        {
            return;
        }

        UIElement container = VisualTreeHelper.GetParent(this.ApplicationDock) as UIElement;
        Point relativeLocation = this.ApplicationDock.TranslatePoint(new Point(0, 0), container);
        }

    protected override Size MeasureOverride(Size availableSize)
    {
        Size size = base.MeasureOverride(availableSize);
        ResizeEmbeddedApp();
        return size;
    }
}
c#
wpf
window
exe
hwnd
asked on Stack Overflow Nov 8, 2016 by Caleb • edited May 23, 2017 by Community

1 Answer

2

I have the same need as my client wants a dashboard with 4 wpf apps running. I believe this should help and by the way if anyone knows a better way to build a windows app dashboard I would love to hear about it. The key is the use of the windowsformhost.

string exeName = @"C:\Repos\OnSpot17\OnTheSpot\bin\Debug\OnTheSpot.exe";
        var procInfo = new System.Diagnostics.ProcessStartInfo(exeName);
        procInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(exeName);
        procInfo.WindowStyle = ProcessWindowStyle.Minimized;
        // Start the process
        _childp = Process.Start(procInfo);
        System.Windows.Forms.Panel _pnlSched = new System.Windows.Forms.Panel();
        WindowsFormsHost windowsFormsHost1 = new WindowsFormsHost();

        windowsFormsHost1.Child = _pnlSched;

        test.Children.Add(windowsFormsHost1);

        // Wait for process to be created and enter idle condition
        //    _childp.WaitForInputIdle();
        // The main window handle may be unavailable for a while, just wait for it
        while (_childp.MainWindowHandle == IntPtr.Zero)
        {
            Thread.Yield();
        }

        // Get the main handle
        _appWin = _childp.MainWindowHandle;
  //      PR.WaitForInputIdle(); // true if the associated process has reached an idle state.
        SetParent(_appWin, _pnlSched.Handle); // loading exe to the wpf window.
answered on Stack Overflow Aug 15, 2017 by John Mcfetridge

User contributions licensed under CC BY-SA 3.0