WPF中用于嵌入其他进程窗口的自定义控件(AppContainer)

简介: 原文:WPF中用于嵌入其他进程窗口的自定义控件(AppContainer) 版权声明:本文为博主原创文章,转载请注明作者和出处 https://blog.csdn.net/ZZZWWWPPP11199988899/article/details/78131292        在Windows上开发客户端程序的时候,有时候我们希望能将其他进程的窗口嵌入到我们自己的程序窗口中,从视觉效果上看就像是其他进程的窗口时我们自己的程序窗口的一部分。
+关注继续查看
原文:WPF中用于嵌入其他进程窗口的自定义控件(AppContainer)

版权声明:本文为博主原创文章,转载请注明作者和出处 https://blog.csdn.net/ZZZWWWPPP11199988899/article/details/78131292


       在Windows上开发客户端程序的时候,有时候我们希望能将其他进程的窗口嵌入到我们自己的程序窗口中,从视觉效果上看就像是其他进程的窗口时我们自己的程序窗口的一部分。具体的思路是,调用Windows API的SetParent方法,设置外部进程主窗口的父容器设置为我们自己的程序容器句柄。

       在Winforms程序中,很容易实现此功能。但是在WPF中会稍微麻烦一点,因为WPF的容器控件是没有自己的独立的句柄的。因此解决思路为先在WPF中嵌入一个Winform的Panel控件(Winform中的Panel控件有自己独立的句柄),然后再将Panel控件的句柄设置为外部程序主窗口的父容器。

       为了便于复用,我将相关的功能整理后封装成了一个WPF自定义控件。


       一 代码结构

       

       如上图,整个控件的代码结构分为三部分:一是控件的默认模板AppContainer.xaml,二是控件的逻辑控制代码,包括一些对外接口方法的类AppContainer.cs,三是c#调用Win32Api的接口类Win32Api.cs。


       二 默认模板

       AppContainer的默认模板非常的简单,模板中只有一个WindowsFormsHost控件,此控件用来存放Winform的Panel控件。

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:wfi ="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"
    xmlns:local="clr-namespace:AppContainers">
    <Style TargetType="{x:Type local:AppContainer}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:AppContainer}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <Grid>
                            <wfi:WindowsFormsHost x:Name="PART_Host"/>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

       三 Win32Api

       主要用到了Win32Api的SetParent方法来设置被嵌入程序的父容器句柄以及MoveWindow来设置被嵌入程序在容器中的位置。

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


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


       四 逻辑控制
       1 控件的初始化

       如代码所以,在复写控件的OnApplyTemplate方法的时候,通过GetTemplateChild方法找到模板中的WindowsFormHost控件,当其不为空的时候,实例化Winform的Panel控件,并将其添加到WindowsFormHost中去。

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            _winFormHost = GetTemplateChild("PART_Host") as WindowsFormsHost;
            if(_winFormHost != null)
            {
                _hostPanel = new System.Windows.Forms.Panel();
                _winFormHost.Child = _hostPanel;
            }
        }
 

       2 外部窗口的嵌入

       外部窗口的嵌入方法有两个:一个是给定程序路径,让控件启动并嵌入程序;一个是当被嵌入程序已经启动时,直接传入已经启动的被嵌程序的进程,然后调用嵌入进程的接口嵌入程序。


       启动并嵌入外部进程的方法:

        public bool StartAndEmbedProcess(string processPath)
        {
            bool isStartAndEmbedSuccess = false;
            _eventDone.Reset();

            //启动进程
            _process = StartApp(processPath);
            if (_process == null)
            {
                return false;
            }

            //确保可获取到句柄
            Thread thread = new Thread(new ThreadStart(() =>
            {
                while (true)
                {
                    if (_process.MainWindowHandle != (IntPtr)0)
                    {
                        _eventDone.Set();
                        break;
                    }
                    Thread.Sleep(10);
                }
            }));
            thread.Start();

            //嵌入进程
            if (_eventDone.WaitOne(10000))
            {
                isStartAndEmbedSuccess = EmbedApp(_process);
                if (!isStartAndEmbedSuccess)
                {
                    CloseApp(_process);
                }
            }
            return isStartAndEmbedSuccess;
        }

       直接嵌入外部进程的方法:

        public bool EmbedExistProcess(Process process)
        {
            _process = process;
            return EmbedApp(process);
        }


       嵌入进程的方法:

        /// <summary>
        /// 将外进程嵌入到当前程序
        /// </summary>
        /// <param name="process"></param>
        private bool EmbedApp(Process process)
        {
            //是否嵌入成功标志,用作返回值
            bool isEmbedSuccess = false;
            //外进程句柄
            IntPtr processHwnd = process.MainWindowHandle;
            //容器句柄
            IntPtr panelHwnd = _hostPanel.Handle;

            if (processHwnd != (IntPtr)0 && panelHwnd != (IntPtr)0)
            {
                //把本窗口句柄与目标窗口句柄关联起来
                int setTime = 0;
                while (!isEmbedSuccess && setTime < 10)
                {
                    isEmbedSuccess = (Win32Api.SetParent(processHwnd, panelHwnd) != 0);
                    Thread.Sleep(100);
                    setTime++;
                }
                //设置初始尺寸和位置
                Win32Api.MoveWindow(_process.MainWindowHandle, 0, 0, (int)ActualWidth, (int)ActualHeight, true);
            }

            if(isEmbedSuccess)
            {
                _embededWindowHandle = _process.MainWindowHandle;
            }

            return isEmbedSuccess;
        }

       3 当外部程序放大缩小时,被嵌入程序窗口界面要能跟着改变,所以要复写OnRender方法,在方法中调用MoveWindow方法来设置被嵌程序的初始位置和大小

        protected override void OnRender(DrawingContext drawingContext)
        {
            if (_process != null)
            {
                Win32Api.MoveWindow(_process.MainWindowHandle, 0, 0, (int)ActualWidth, (int)ActualHeight, true);
            }
            base.OnRender(drawingContext);
        }

        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            this.InvalidateVisual();
            base.OnRenderSizeChanged(sizeInfo);
        }
       4 当外部程序关闭时,要能同时关闭被嵌入进程。

        /// <summary>
        /// 关闭进程
        /// </summary>
        /// <param name="process"></param>
        private void CloseApp(Process process)
        {
            if (process != null && !process.HasExited)
            {
                process.Kill();
            }
        }

        public void CloseProcess()
        {
            CloseApp(_process);
        }

       五 控件的应用

<Window x:Class="WpfAppContainerTest.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:WpfAppContainerTest"
        xmlns:container="clr-namespace:AppContainers;assembly=AppContainers"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <container:AppContainer x:Name="ctnTest" Margin="20"/>
    </Grid>
</Window>


       窗口载入的时候嵌入Windows自带的画图程序。
        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            if (!_isLoadSuccess)
            {
                _isLoadSuccess = ctnTest.StartAndEmbedProcess(@"C:\Windows\system32\mspaint.exe");
            }
        }


       效果图:

       


源代码

      

目录
相关文章
|
2月前
|
人工智能 C#
WPF自定义控件库之Window窗口
本文以自定义窗口为例,简述WPF开发中如何通过自定义控件来扩展功能和样式,仅供学习分享使用,如有不足之处,还请指正。
69 5
|
10月前
|
前端开发 C#
WPF MVVM 如何在 ViewModel 中关闭界面窗口
WPF MVVM 如何在 ViewModel 中关闭界面窗口
|
10月前
|
C#
Revit 二次开发添加WPF窗口的办法
Revit 二次开发添加WPF窗口的办法
Revit 二次开发添加WPF窗口的办法
|
C#
WPF 同一窗口内的多线程/多进程 UI(使用 SetParent 嵌入另一个窗口)
原文 WPF 同一窗口内的多线程/多进程 UI(使用 SetParent 嵌入另一个窗口) WPF 的 UI 逻辑只在同一个线程中,这是学习 WPF 开发中大家几乎都会学习到的经验。如果希望做不同线程的 UI,大家也会想到使用另一个窗口来实现,让每个窗口拥有自己的 UI 线程。
2682 0
|
C#
自定义WPF 窗口样式
原文:自定义WPF 窗口样式 自定义 Window 在客户端程序中,经常需要用到自定义一个 Window ,大部分是为了好看吧。
1240 0
|
Web App开发 C# Windows
WPF 使用 WindowChrome,在自定义窗口标题栏的同时最大程度保留原生窗口样式(类似 UWP/Chrome)
原文:WPF 使用 WindowChrome,在自定义窗口标题栏的同时最大程度保留原生窗口样式(类似 UWP/Chrome) 版权声明:本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
1464 0
|
前端开发 C#
使用MVVM DataTriggers在WPF XAML视图之间切换/Window窗口自适应内容大小并居中
原文 使用MVVM DataTriggers在WPF XAML视图之间切换 相关文章: http://www.technical-recipes.com/2016/switching-between-wpf-xaml-views-using-mvvm-datatemplate/ 这篇文章解决了能够根据ViewModel类的属性在不同视图之间切换的问题。
1724 0
|
C# 前端开发 JavaScript
WPF绘制自定义窗口
原文:WPF绘制自定义窗口 WPF是制作界面的一大利器,下面就用WPF模拟一下360的软件管理界面,360软件管理界面如下:   界面不难,主要有如下几个要素: 窗体的圆角 自定义标题栏及按钮 自定义状态栏 窗体的半透明效果 窗体4周有一圈半透明阴影(抓的图上看不出来) 实现思路很简单,首先隐藏默认窗口的标题栏和边框,然后用WPF的Border或Canvas等元素模拟定义窗体的标题栏、内容区和状态栏。
1420 0
|
C#
wpf 自定义窗口,最大化时覆盖任务栏解决方案
原文:wpf 自定义窗口,最大化时覆盖任务栏解决方案 相信很多人使用wpf时会选择自定义美观的窗口,因此会设置WindowStyle="None" 取消自带的标题栏。但这样使用 WindowState="Maximized" 或者后台 this.WindowState = System.Windows.WindowState.Maximized; 最大化窗口会覆盖掉系统任务栏,即全屏了。
2262 0
|
安全 C#
WPF的消息机制(三)- WPF内部的5个窗口之处理激活和关闭的消息窗口以及系统资源通知窗口
原文:WPF的消息机制(三)- WPF内部的5个窗口之处理激活和关闭的消息窗口以及系统资源通知窗口 版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.
990 0
相关产品
云迁移中心
推荐文章
更多