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控件。
<ResourceDictionaryxmlns="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>
private void MainWindow_Loaded(object sender, RoutedEventArgs e){if (!_isLoadSuccess){_isLoadSuccess = ctnTest.StartAndEmbedProcess(@"C:\Windows\system32\mspaint.exe");}}
? ? ? ?
源代碼
? ? ??
總結
以上是生活随笔為你收集整理的WPF中用于嵌入其他进程窗口的自定义控件(AppContainer)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA文件写入FileWriter
- 下一篇: Linux编程 20 shell编程(