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 路由事件(主要是隧道和冒泡)的存在,我们很容易能够通过只监听窗口中的某些事件使得整个窗口中所有控件发生的事件都被 ...
随机推荐
- 【20.23%】【codeforces 740A】Alyona and copybooks
time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...
- [TypeStyle] Use TypeStyle keyframes to create CSS animations
We cover CSS keyframes and how to create them using TypeStyle. We then show how to use the keyframes ...
- Swift3.0 功能一(持续更新)
修改项目名称两种方式 1.Bundle name 2.Bundle display name try 三种处理异常的方式 // 在swift中提供三种处理异常的方式 // 方式一:try方式 程序员手 ...
- TextView之一:子类的常用属性 分类: H1_ANDROID 2013-10-30 15:14 770人阅读 评论(0) 收藏
TextView常见的子类包括EditText,Button,CheckBox, RadioButton等. 1.EditText EditText继承自TextView,因此TextView所有属性 ...
- mysqldump备份脚本
#!/bin/bash # 10 23 * * * /bin/bash /data/script/backup_mysqldump.sh BDATE=`date +%Y%m%d%H%M%S`BPATH ...
- css 翻牌 翻转 3d翻转 特效
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- php curl 添加cookie伪造登陆抓取数据(摘自网络)
有的网页必须登陆才能看到,这个时候想要抓取信息必须在header里面传递cookie值才能获取 1.首先登陆网站,打开firebug就能看到对应的cookie把这些cookie拷贝出来就能使用了 2. ...
- INotifyPropertyChanged接口的详细说明
在windows phone开发8.1:数据绑定中,我们了解了数据绑定的基本知识.今后几篇文章会继续深入了解数据绑定.今天我们来看在数据绑定中十分重要的INotifyPropertyChanged接口 ...
- acdream 1430 SETI 后缀数组+height分组
这题昨天比赛的时候逗了,后缀想不出来,由于n^2的T了,就没往后缀数组想--并且之后解题的人又说用二分套二分来做.然后就更不会了-- 刚才看了题解,唉--原来题讲解n^2的也能够过,然后就--这样了! ...
- 于 Android NDK 的学习之旅-----数据传输(基本数据类型和数组传输)
之前的一些文章都有涉及到上层和中间层的数据传输,简单来说,也就是参数和返回值的使用.因为中间层要做的最多的也就是数据传输与转换,下面来介绍下这方面的知识. 数据传输可分为 基本数据类型传输 和 引用数 ...