C# wpf 嵌入外部程序

WPF Hwnd窗口互操作系列

第一章 嵌入Hwnd窗口
第二章 嵌入WinForm控件
第三章 嵌入WPF控件
第四章 嵌入外部程序(本章)
第五章 底部嵌入HwndHost


文章目录


前言

实现嵌入各种窗口控件后,其实还会有一种需求:嵌入外部程序,我们有时可能需要嵌入一个浏览器或者或者播放器等一些已有的程序,其嵌入原理也和前面差不多,只要能获取进程的主窗口句柄,然后将窗口嵌入。


一、如何实现?

1、定义属性

定义一个依赖属性,提供给xaml设置进程运行的命令行

csharp 复制代码
public class AppHost : HwndHost
{
    /// <summary>
    /// 进程运行的命令行
    /// </summary>
    public string Cmdline
    {
        get { return (string)GetValue(CmdlineProperty); }
        set { SetValue(CmdlineProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Cmdline.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty CmdlineProperty =
        DependencyProperty.Register("Cmdline", typeof(string), typeof(AppHost), new PropertyMetadata(""));
}        

2、进程嵌入

在下列方法中进行进程嵌入,具体操作如下列步骤。

csharp 复制代码
protected override HandleRef BuildWindowCore(HandleRef hwndParent)

(1)启动进程

csharp 复制代码
var cmds = Cmdline.Split(" ", 2);
Process? _process;
_process.StartInfo.FileName = cmds.First();
_process.StartInfo.Arguments = cmds.Last();
_process.StartInfo.UseShellExecute = false;
_process.StartInfo.CreateNoWindow = true;
_process.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
_process.Start();

(2)、进程加入作业对象

这个步骤是用于管理进程,确保《子进程跟随主进程关闭》

csharp 复制代码
static Job _job = new Job();
csharp 复制代码
_job.AddProcess(_process.Handle);

(3)、获取主窗口句柄

下列提供的是简单获取主窗口句柄的方法。通过延时等待的方式获取。需要精确时间获取主窗口句柄则可以使用钩子,在子进程窗口创建事件中获取句柄。

csharp 复制代码
for (int i = 0; i < 200 && _process.MainWindowHandle == 0; i++) Thread.Sleep(5);
if (_process.MainWindowHandle == 0)
{
    throw new Exception("process no window");
}
return new HandleRef(this, Handle);

3、销毁进程

csharp 复制代码
protected override void DestroyWindowCore(HandleRef hwnd)
{
    _process?.Kill();
    _process?.Dispose();
    _process = null;
}

二、完整代码

其中Job对象在《子进程跟随主进程关闭》中。

AppHost.cs

csharp 复制代码
using JobManagement;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using Process = System.Diagnostics.Process;
using TextBox = System.Windows.Controls.TextBox;
using Thread = System.Threading.Thread;

namespace WpfHwndElement
{
    /// <summary>
    /// 需要手动dispose此控件。
    /// </summary>
    public class AppHost : HwndHost
    {
        static Job _job = new Job();
        Process? _process;
        /// <summary>
        /// 进程运行的命令行
        /// </summary>
        public string Cmdline
        {
            get { return (string)GetValue(CmdlineProperty); }
            set { SetValue(CmdlineProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Cmdline.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CmdlineProperty =
            DependencyProperty.Register("Cmdline", typeof(string), typeof(AppHost), new PropertyMetadata(""));

        new public IntPtr Handle
        {
            get { return (IntPtr)GetValue(HandleProperty); }
            private set { SetValue(HandleProperty, value); }
        }
        // Using a DependencyProperty as the backing store for Hwnd.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HandleProperty =
            DependencyProperty.Register("Handle", typeof(IntPtr), typeof(NativeHost), new PropertyMetadata(IntPtr.Zero));
        protected override HandleRef BuildWindowCore(HandleRef hwndParent)
        {
            try
            {
                if (DesignerProperties.GetIsInDesignMode(this)) throw new Exception("design mode won't show app");
                var cmds = Cmdline.Split(" ", 2);
                _process = new Process();
                _process.StartInfo.FileName = cmds.First();
                _process.StartInfo.Arguments = cmds.Length > 1 ? cmds.Last() : "";
                _process.StartInfo.UseShellExecute = false;
                _process.StartInfo.CreateNoWindow = true;
                _process.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
                _process.Start();
                _job.AddProcess(_process.Handle);
                for (int i = 0; i < 200 && _process.MainWindowHandle == 0; i++) Thread.Sleep(5);
                if (_process.MainWindowHandle == 0)
                {
                    throw new Exception("process no window");
                }
                Handle = _process.MainWindowHandle;
                var wndStyle = GetWindowLong(Handle, GWL_STYLE);
                wndStyle &= ~WS_THICKFRAME;
                wndStyle &= ~WS_CAPTION;
                SetWindowLong(Handle, GWL_STYLE, wndStyle | WS_CHILD);
                SetParent(Handle, hwndParent.Handle);
            }
            catch (Exception ex)
            {
                var window = new Window() { Width = 0, Height = 0, ResizeMode = ResizeMode.NoResize, WindowStyle = WindowStyle.None, Content = new TextBox() { IsReadOnly = true, Text = ex.Message + " " + ex.StackTrace, TextWrapping = TextWrapping.Wrap } };
                var hwnd = new WindowInteropHelper(window).EnsureHandle();
                window.Show();
                SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) | WS_CHILD);
                SetParent(hwnd, hwndParent.Handle);
                Handle = hwnd;
            }
            return new HandleRef(this, Handle);
        }
        protected override void DestroyWindowCore(HandleRef hwnd)
        {
            var window = HwndSource.FromHwnd(hwnd.Handle)?.RootVisual as Window;
            window?.Close();
            _process?.Kill();
            _process?.Dispose();
            _process = null;
        }
        const int WS_CAPTION = 0x00C00000;
        const int WS_THICKFRAME = 0x00040000;
        const int WS_CHILD = 0x40000000;
        const int GWL_STYLE = (-16);
        [DllImport("user32.dll", EntryPoint = "GetWindowLongW")]
        static extern int GetWindowLong(IntPtr hwnd, int nIndex);
        [DllImport("user32.dll", EntryPoint = "SetWindowLongW")]
        static extern int SetWindowLong(IntPtr hwnd, int nIndex, int dwNewLong);
        [DllImport("user32.dll")]
        public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
    }
}

三、使用示例

1、嵌入ffplay.exe

MainWindow.xaml

xml 复制代码
<Window x:Class="WpfHwndElement.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:WpfHwndElement"
        mc:Ignorable="d"
        Title="MainWindow" Height="360" Width="640"       
        >
    <Grid>
        <local:AppHost Cmdline="ffplay" Width="200" Height="200"></local:AppHost>
    </Grid>
</Window>

效果预览


总结

以上就是今天要讲的内容,嵌入外部程序还是相对比较容易实现的,而且也有一定的使用场景。创建进程,并能获取到进程的主窗口句柄即可。另外要注意的是管理子进程的退出,其他都问题不大。

相关推荐
冷眼Σ(-᷅_-᷄๑)2 小时前
WPF缩放动画和平移动画叠加后会发生什么?
wpf·动画
△曉風殘月〆4 小时前
WPF MVVM入门系列教程(二、依赖属性)
c#·wpf·mvvm
逐·風6 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
hairenjing11238 小时前
使用 Mac 数据恢复从 iPhoto 图库中恢复照片
windows·stm32·嵌入式硬件·macos·word
m0_656974749 小时前
C#中的集合类及其使用
开发语言·c#
九鼎科技-Leo10 小时前
了解 .NET 运行时与 .NET 框架:基础概念与相互关系
windows·c#·.net
九鼎科技-Leo12 小时前
什么是 ASP.NET Core?与 ASP.NET MVC 有什么区别?
windows·后端·c#·asp.net·mvc·.net
.net开发12 小时前
WPF怎么通过RestSharp向后端发请求
前端·c#·.net·wpf
九鼎科技-Leo12 小时前
WPF 中 NavigationWindow 与 Page 的继承关系解析
wpf
小乖兽技术12 小时前
C#与C++交互开发系列(二十):跨进程通信之共享内存(Shared Memory)
c++·c#·交互·ipc