[WPF] 在 WPF 視窗內嵌入其他程式

以下將介紹如何將其他程式 (.exe) 內嵌到 WPF 程式內

1. 在 WPF 專案內新增一個名為 [HostControl] 的使用者控制項

2. 在 HostControl.xaml.cs 內使用 DllImport 引用必要的函數

using System;
using System.Runtime.InteropServices;
using System.Windows.Controls;

namespace HostDemo
{
    public partial class HostControl : UserControl, IDisposable
    {
        [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
        public struct HWND__
        {
            /// int
            public int unused;
        }

        [DllImport("user32.dll", EntryPoint = "GetWindowThreadProcessId", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        private static extern long GetWindowThreadProcessId(long hWnd, long lpdwProcessId);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

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

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

        [DllImport("user32.dll", EntryPoint = "SetWindowLongA", SetLastError = true)]
        public static extern int SetWindowLongA([System.Runtime.InteropServices.InAttribute()] System.IntPtr hWnd, int nIndex, int dwNewLong);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern long SetWindowPos(IntPtr hwnd, long hWndInsertAfter, long x, long y, long cx, long cy, long wFlags);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool MoveWindow(IntPtr hwnd, int x, int y, int cx, int cy, bool repaint);

        internal delegate int WindowEnumProc(IntPtr hwnd, IntPtr lparam);

        [DllImport("user32.dll")]
        internal static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam);

        [DllImport("user32.dll")]
        private static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

        [DllImport("Shcore.dll")]
        private static extern int SetProcessDpiAwareness(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

        private const int SWP_NOOWNERZORDER = 0x200;
        private const int SWP_NOREDRAW = 0x8;
        private const int SWP_NOZORDER = 0x4;
        private const int SWP_SHOWWINDOW = 0x0040;
        private const int WS_EX_MDICHILD = 0x40;
        private const int SWP_FRAMECHANGED = 0x20;
        private const int SWP_NOACTIVATE = 0x10;
        private const int SWP_ASYNCWINDOWPOS = 0x4000;
        private const int SWP_NOMOVE = 0x2;
        private const int SWP_NOSIZE = 0x1;
        private const int GWL_STYLE = (-16);
        private const int WS_VISIBLE = 0x10000000;
        private const int WS_CHILD = 0x40000000;
        private const int WM_ACTIVATE = 0x0006;
        private readonly IntPtr WA_ACTIVE = new IntPtr(1);
        private readonly IntPtr WA_INACTIVE = new IntPtr(0);
    }
}

 

3. 在 HostControl.xaml.cs 內加入 [呼叫其他程式] 與 [內嵌其他程式] 的邏輯

namespace HostDemo
{
    public partial class HostControl : UserControl, IDisposable
    {
        public event EventHandler HostProcessStarted;

        private void OnHostProcessStarted()
        {
            HostProcessStarted?.Invoke(this, EventArgs.Empty);
        }

        /// <summary>
        /// Track if the application has been created
        /// </summary>
        public bool IsCreated { get; private set; } = false;

        public string ExeName { get; set; }

        /// <summary>
        /// Track if the control is disposed
        /// </summary>
        private bool isDisposed = false;

        /// <summary>
        /// Handle to the application Window
        /// </summary>
        private IntPtr appWin;

        private Process childp;

        public HostControl()
        {
            InitializeComponent();
            this.Loaded += HostControl_Loaded;
            this.Unloaded += HostControl_Unloaded;
            this.SizeChanged += HostControl_SizeChanged;
        }

        ~HostControl()
        {
            this.Dispose();
        }

        private void HostControl_Loaded(object sender, RoutedEventArgs e)
        {
            if (IsCreated)
            {
                return;
            }

            if (string.IsNullOrEmpty(ExeName))
            {
                return;
            }

            Application.Current.Exit -= Current_Exit;
            Application.Current.Exit += Current_Exit;

            appWin = IntPtr.Zero;

            try
            {
                var procInfo = new ProcessStartInfo(this.ExeName);
                procInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(this.ExeName);

                childp = Process.Start(procInfo);

                IsCreated = true;

                childp.WaitForInputIdle();

                this.appWin = childp.MainWindowHandle;
                var helper = new WindowInteropHelper(Window.GetWindow(this));

                SetParent(appWin, helper.Handle);
                SetWindowLongA(appWin, GWL_STYLE, WS_VISIBLE);

                if (childp != null && childp.HasExited == false)
                {
                    OnHostProcessStarted();
                }

                UpdateSize();
            }
            catch (Exception ex)
            {
                Debug.Print(ex.Message + "Error");

                // 出錯了,把自己隱藏起來
                this.Visibility = Visibility.Collapsed;
            }
        }

        private void HostControl_Unloaded(object sender, RoutedEventArgs e)
        {
            Application.Current.Exit -= Current_Exit;
            this.Dispose();
        }

        private void Current_Exit(object sender, ExitEventArgs e)
        {
            this.Dispose();
        }

        private void HostControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            UpdateSize();
        }

        private void UpdateSize()
        {
            if (this.appWin != IntPtr.Zero)
            {
                PresentationSource source = PresentationSource.FromVisual(this);

                var scaleX = 1D;
                var scaleY = 1D;
                if (source != null)
                {
                    scaleX = source.CompositionTarget.TransformToDevice.M11;
                    scaleY = source.CompositionTarget.TransformToDevice.M22;
                }

                var width = (int)(this.ActualWidth * scaleX);
                var height = (int)(this.ActualHeight * scaleY);

                MoveWindow(appWin, 0, 0, width, height, true);
            }
        }

        protected void Dispose(bool disposing)
        {
            if (!isDisposed)
            {
                if (disposing)
                {
                    if (IsCreated && childp != null && !childp.HasExited)
                    {
                        childp.Kill();
                    }

                    if (appWin != IntPtr.Zero)
                    {
                        appWin = IntPtr.Zero;
                    }
                }

                isDisposed = true;
            }
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}

 

 

至此便完成了 HostControl 控制項的建置,以下介紹如何使用此控制項

 

在任一頁面中加入 HostControl,並將 HostControl.ExeName 填入指定程式的檔名,即可將該程式內嵌進來

<Page x:Class="HostDemo.Page1"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:HostDemo"
      mc:Ignorable="d" 
      Title="Page1">

    <Grid>
        <local:HostControl ExeName="notepad.exe" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
    </Grid>
</Page>

 

完整原始碼

https://github.com/renewal-wu/EmbeddedExeInWPF

 

參考資料

https://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=673701