原文: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控件。

  1. <ResourceDictionary
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:wfi ="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"
  5. xmlns:local="clr-namespace:AppContainers">
  6. <Style TargetType="{x:Type local:AppContainer}">
  7. <Setter Property="Template">
  8. <Setter.Value>
  9. <ControlTemplate TargetType="{x:Type local:AppContainer}">
  10. <Border Background="{TemplateBinding Background}"
  11. BorderBrush="{TemplateBinding BorderBrush}"
  12. BorderThickness="{TemplateBinding BorderThickness}">
  13. <Grid>
  14. <wfi:WindowsFormsHost x:Name="PART_Host"/>
  15. </Grid>
  16. </Border>
  17. </ControlTemplate>
  18. </Setter.Value>
  19. </Setter>
  20. </Style>
  21. </ResourceDictionary>

       三 Win32Api

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

  1. [DllImport("user32.dll", SetLastError = true)]
  2. public static extern int SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
  3. [DllImport("user32.dll", SetLastError = true)]
  4. public static extern bool MoveWindow(IntPtr hwnd, int x, int y, int cx, int cy, bool repaint);

       四 逻辑控制

       1 控件的初始化

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

  1. public override void OnApplyTemplate()
  2. {
  3. base.OnApplyTemplate();
  4. _winFormHost = GetTemplateChild("PART_Host") as WindowsFormsHost;
  5. if(_winFormHost != null)
  6. {
  7. _hostPanel = new System.Windows.Forms.Panel();
  8. _winFormHost.Child = _hostPanel;
  9. }
  10. }

 

       2 外部窗口的嵌入

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

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

  1. public bool StartAndEmbedProcess(string processPath)
  2. {
  3. bool isStartAndEmbedSuccess = false;
  4. _eventDone.Reset();
  5. //启动进程
  6. _process = StartApp(processPath);
  7. if (_process == null)
  8. {
  9. return false;
  10. }
  11. //确保可获取到句柄
  12. Thread thread = new Thread(new ThreadStart(() =>
  13. {
  14. while (true)
  15. {
  16. if (_process.MainWindowHandle != (IntPtr)0)
  17. {
  18. _eventDone.Set();
  19. break;
  20. }
  21. Thread.Sleep(10);
  22. }
  23. }));
  24. thread.Start();
  25. //嵌入进程
  26. if (_eventDone.WaitOne(10000))
  27. {
  28. isStartAndEmbedSuccess = EmbedApp(_process);
  29. if (!isStartAndEmbedSuccess)
  30. {
  31. CloseApp(_process);
  32. }
  33. }
  34. return isStartAndEmbedSuccess;
  35. }

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

  1. public bool EmbedExistProcess(Process process)
  2. {
  3. _process = process;
  4. return EmbedApp(process);
  5. }

       嵌入进程的方法:

  1. /// <summary>
  2. /// 将外进程嵌入到当前程序
  3. /// </summary>
  4. /// <param name="process"></param>
  5. private bool EmbedApp(Process process)
  6. {
  7. //是否嵌入成功标志,用作返回值
  8. bool isEmbedSuccess = false;
  9. //外进程句柄
  10. IntPtr processHwnd = process.MainWindowHandle;
  11. //容器句柄
  12. IntPtr panelHwnd = _hostPanel.Handle;
  13. if (processHwnd != (IntPtr)0 && panelHwnd != (IntPtr)0)
  14. {
  15. //把本窗口句柄与目标窗口句柄关联起来
  16. int setTime = 0;
  17. while (!isEmbedSuccess && setTime < 10)
  18. {
  19. isEmbedSuccess = (Win32Api.SetParent(processHwnd, panelHwnd) != 0);
  20. Thread.Sleep(100);
  21. setTime++;
  22. }
  23. //设置初始尺寸和位置
  24. Win32Api.MoveWindow(_process.MainWindowHandle, 0, 0, (int)ActualWidth, (int)ActualHeight, true);
  25. }
  26. if(isEmbedSuccess)
  27. {
  28. _embededWindowHandle = _process.MainWindowHandle;
  29. }
  30. return isEmbedSuccess;
  31. }

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

  1. protected override void OnRender(DrawingContext drawingContext)
  2. {
  3. if (_process != null)
  4. {
  5. Win32Api.MoveWindow(_process.MainWindowHandle, 0, 0, (int)ActualWidth, (int)ActualHeight, true);
  6. }
  7. base.OnRender(drawingContext);
  8. }
  9. protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
  10. {
  11. this.InvalidateVisual();
  12. base.OnRenderSizeChanged(sizeInfo);
  13. }

       4 当外部程序关闭时,要能同时关闭被嵌入进程。

  1. /// <summary>
  2. /// 关闭进程
  3. /// </summary>
  4. /// <param name="process"></param>
  5. private void CloseApp(Process process)
  6. {
  7. if (process != null && !process.HasExited)
  8. {
  9. process.Kill();
  10. }
  11. }

  1. public void CloseProcess()
  2. {
  3. CloseApp(_process);
  4. }

       五 控件的应用

  1. <Window x:Class="WpfAppContainerTest.MainWindow"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6. xmlns:local="clr-namespace:WpfAppContainerTest"
  7. xmlns:container="clr-namespace:AppContainers;assembly=AppContainers"
  8. mc:Ignorable="d"
  9. Title="MainWindow" Height="350" Width="525">
  10. <Grid>
  11. <container:AppContainer x:Name="ctnTest" Margin="20"/>
  12. </Grid>
  13. </Window>

       窗口载入的时候嵌入Windows自带的画图程序。

  1. private void MainWindow_Loaded(object sender, RoutedEventArgs e)
  2. {
  3. if (!_isLoadSuccess)
  4. {
  5. _isLoadSuccess = ctnTest.StartAndEmbedProcess(@"C:\Windows\system32\mspaint.exe");
  6. }
  7. }

       效果图:

       

源代码

      

WPF中用于嵌入其他进程窗口的自定义控件(AppContainer)的更多相关文章

  1. WPF中利用后台代码实现窗口分栏动态改变

    在WPF中实现窗口分栏并能够通过鼠标改变大小已经非常容易,例如将一个GRID分成竖排三栏显示,就可以将GRID先分成5列,其中两个固定列放GridSplitter. <Grid Backgrou ...

  2. C# WPF 中WebBrowser拖动来移动窗口,改变窗口位置

    前言 wpf中的WebBrowser相比之前的winform阉割了不少东西,也增加了不少东西,但是msdn对wpf也没有较好的文档 WebBrowser可以说是一个.NET控件,相对于WPF中的控件, ...

  3. WPF应用程序嵌入第三方exe

    把其它应用嵌入到C#窗口 源代码-CSDN下载 https://download.csdn.net/download/aiqinghee/10652732 WPF应用程序嵌入第三方exe - gao2 ...

  4. WPF 同一窗口内的多线程/多进程 UI(使用 SetParent 嵌入另一个窗口)

    原文 WPF 同一窗口内的多线程/多进程 UI(使用 SetParent 嵌入另一个窗口) WPF 的 UI 逻辑只在同一个线程中,这是学习 WPF 开发中大家几乎都会学习到的经验.如果希望做不同线程 ...

  5. WPF中嵌入普通Win32程序的方法

    公司现在在研发基于.Net中WPF技术的产品,由于要兼容旧有产品,比如一些旧有的Win32程序.第三方的Win32程序等等,还要实现自动登录这些外部Win32程序,因此必须能够将这些程序整合到我们的系 ...

  6. 【转】WPF中的窗口的生命周期

    原文地址:http://www.cnblogs.com/Jennifer/articles/1997763.html WPF中的窗口的生命周期 WPF中所有窗口的基类型都是System.Windows ...

  7. VS编程,WPF中,获取鼠标相对于当前程序窗口的坐标的一种方法

    原文:VS编程,WPF中,获取鼠标相对于当前程序窗口的坐标的一种方法 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/article/ ...

  8. 在WPF中嵌入WebBrowser可视化页面

    无论是哪种C/S技术,涉及数据可视化就非常的累赘了,当然大神也一定有,只不过面向大多数人,还是通过网页来实现,有的时候不想把这两个功能分开,一般会是客户的原因,所以我们打算在WPF中嵌入WebBrow ...

  9. 如何监视 WPF 中的所有窗口,在所有窗口中订阅事件或者附加 UI

    原文:如何监视 WPF 中的所有窗口,在所有窗口中订阅事件或者附加 UI 由于 WPF 路由事件(主要是隧道和冒泡)的存在,我们很容易能够通过只监听窗口中的某些事件使得整个窗口中所有控件发生的事件都被 ...

随机推荐

  1. GB2312/ANSI编码转中文字符

    struct GB2312{    unsigned short gb2312code;    CString     ChineseCode;}GB2312ToChinese[] = {{0xA1A ...

  2. JAVA中try-catch异常逃逸

    有时候一些小的细节,确实比较纠结,对于try-catch-finally代码块中代码依次执行,当try中有exception抛出时,将会有catch拦截并执行,如果没有catch区块,那么except ...

  3. ios开发runtime学习四:动态添加属性

    #import "ViewController.h" #import "Person.h" #import "NSObject+Property.h& ...

  4. 单选框radio改变事件详解(用的jquery的radio的change事件)

    单选框radio改变事件详解(用的jquery的radio的change事件) 一.总结 1.用的jquery的radio的change事件:当元素的值发生改变时,会发生 change 事件,radi ...

  5. spring mvc 解决csrf跨站请求攻击

    http://www.dewen.net.cn/q/935/spring+mvc+%E8%A7%A3%E5%86%B3csrf%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82% ...

  6. js进阶 11-9/10/11 jquery创建和插入节点

    js进阶 11-9/10/11 jquery创建和插入节点 一.总结 一句话总结: 1.jquery插入节点8个方法? 内部之前,内部之后,之前,之后:各两个 append()和appendTo() ...

  7. Android定位开发之百度定位、高德定位、腾讯定位,三足鼎立一起为我所用!

    这几天的项目不是非常紧.于是想为未来可能要做的项目做一些技术储备. 下一个项目非常有可能是定位开发,须要用到手机定位功能,于是查了查如今比較流行的第三方定位,最火的基本上就是百度定位>高德定位& ...

  8. mui常用功能链接地址

    1.下拉刷新mui.pullToRefresh插件http://ask.dcloud.net.cn/article/12152.打包app权限列表http://ask.dcloud.net.cn/ar ...

  9. Android开发:使用ViewDragHelper实现抽屉拉伸效果

    事实上,有非常多方法能够实现一个Layout的抽屉拉伸效果,最常常的方法就是自己定义一个ViewGroup,然后控制点击事件.控制移动之类的,这样的方法的代码量多,并且实现起来复杂,后期维护添加其它效 ...

  10. Golang快速入门

    Go语言简介: Golang 简称 Go,是一个开源的编程语言,Go是从2007年末由 Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian L ...