WPF中用于嵌入其他进程窗口的自定义控件(AppContainer)
原文:WPF中用于嵌入其他进程窗口的自定义控件(AppContainer)
在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");
}
}
效果图:
WPF中用于嵌入其他进程窗口的自定义控件(AppContainer)的更多相关文章
- WPF中利用后台代码实现窗口分栏动态改变
在WPF中实现窗口分栏并能够通过鼠标改变大小已经非常容易,例如将一个GRID分成竖排三栏显示,就可以将GRID先分成5列,其中两个固定列放GridSplitter. <Grid Backgrou ...
- C# WPF 中WebBrowser拖动来移动窗口,改变窗口位置
前言 wpf中的WebBrowser相比之前的winform阉割了不少东西,也增加了不少东西,但是msdn对wpf也没有较好的文档 WebBrowser可以说是一个.NET控件,相对于WPF中的控件, ...
- WPF应用程序嵌入第三方exe
把其它应用嵌入到C#窗口 源代码-CSDN下载 https://download.csdn.net/download/aiqinghee/10652732 WPF应用程序嵌入第三方exe - gao2 ...
- WPF 同一窗口内的多线程/多进程 UI(使用 SetParent 嵌入另一个窗口)
原文 WPF 同一窗口内的多线程/多进程 UI(使用 SetParent 嵌入另一个窗口) WPF 的 UI 逻辑只在同一个线程中,这是学习 WPF 开发中大家几乎都会学习到的经验.如果希望做不同线程 ...
- WPF中嵌入普通Win32程序的方法
公司现在在研发基于.Net中WPF技术的产品,由于要兼容旧有产品,比如一些旧有的Win32程序.第三方的Win32程序等等,还要实现自动登录这些外部Win32程序,因此必须能够将这些程序整合到我们的系 ...
- 【转】WPF中的窗口的生命周期
原文地址:http://www.cnblogs.com/Jennifer/articles/1997763.html WPF中的窗口的生命周期 WPF中所有窗口的基类型都是System.Windows ...
- VS编程,WPF中,获取鼠标相对于当前程序窗口的坐标的一种方法
原文:VS编程,WPF中,获取鼠标相对于当前程序窗口的坐标的一种方法 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/article/ ...
- 在WPF中嵌入WebBrowser可视化页面
无论是哪种C/S技术,涉及数据可视化就非常的累赘了,当然大神也一定有,只不过面向大多数人,还是通过网页来实现,有的时候不想把这两个功能分开,一般会是客户的原因,所以我们打算在WPF中嵌入WebBrow ...
- 如何监视 WPF 中的所有窗口,在所有窗口中订阅事件或者附加 UI
原文:如何监视 WPF 中的所有窗口,在所有窗口中订阅事件或者附加 UI 由于 WPF 路由事件(主要是隧道和冒泡)的存在,我们很容易能够通过只监听窗口中的某些事件使得整个窗口中所有控件发生的事件都被 ...
随机推荐
- ArcSDE:C#打开SDE数据库的几种方式总结
转自原文 ArcSDE:C#打开SDE数据库的几种方式总结 1.通过指定连接属性参数打开数据库 /// <param name="server">数据库服务器名< ...
- linux的任务计划 定时任务 计划任务
例如要定时访问一个指定的url 先看看系统有没有crontab
- 【苦读官方文档】2.Android应用程序基本原理概述
官方文档原文地址 应用程序原理 Android应用程序是通过Java编程语言来写.Android软件开发工具把你的代码和其它数据.资源文件一起编译.打包成一个APK文件,这个文档以.apk为后缀,保存 ...
- 使用Apache FtpServer搭建FTP服务器 [FlashFXP]
<server xmlns="http://mina.apache.org/ftpserver/spring/v1" xmlns:xsi="http://www.w ...
- USB 3.0规范中译本 第10章 集线器,主机下行口以及设备上行口规范
本文为CoryXie原创译文,转载及有任何问题请联系cory.xie#gmail.com. 本章描述USB 3.0 集线器的体系结构要求.本章还描述主机下行口和集线器下行口之间功能性的不同之处,以及设 ...
- js中动态创建json,动态为json添加属性、属性值的实例
如下所示: ? 1 2 3 4 5 6 7 var param = {}; for(var i=0;i<fields.length;i++){ var field = fields[i]; ...
- Uncaught SyntaxError: Unexpected end of input 解决办法
Unexpected end of input 的英文意思是"意外的终止输入" 他通常表示我们浏览器在读取我们的js代码时,碰到了不可预知的错误,导致浏览器 无语进行下面的读取 ...
- IT忍者神龟之Hibernat持久化对象-数据表映射配置回想
1.持久化对象POJO编写规则: 1) 有空參public构造器: 2) 提供标识属性.映射数据表主键: 3) 属性提供setter和getter方法. 4) 属性使用基本数据类型的包装类型.基本类型 ...
- Oauth入门学习
在一些网站总是看到调用其他网站的信息的实例,比如在人人网中导入MSN联系人,在Facebook中导入gmail,yahoo mail好友,第三方网站不需要总知道你的密码,对于应用的授权完全交给你自己, ...
- Linux下新手怎样将VIM配置成C++编程环境(能够STL自己主动补全)
~ 弄拉老半天,最终弄的几乎相同啦,果然程序猿还是须要有点折腾精神啊. 首先你要安装vim,命令:sudo apt-get install vim vim它仅仅是一个编辑器,它不是IDE(比方code ...