在.NET主程序中,咱們能夠經過建立 ExcelApplication 對象來打開一個Excel應用程序,若是咱們想在Excle裏面再打開WPF窗口,問題就不那麼簡單了。html
咱們能夠簡單的實例化一個WPF窗體對象而後在Office應用程序的窗體上打開這個新的WPF窗體,此時Office應用的窗體就是這個WPF的宿主窗體,這個WPF窗體是Office應用窗體的「子窗體」。而後子窗體跟宿主不是在一個UI線程上,也不在同一個進程上,子窗體極可能會在宿主窗體後面看不到。這個時候須要調用Win32函數,將Office應用的窗體設置爲WPF子窗體的父窗體,讓WPF子窗體成爲真正的「子窗體」。這個函數的形式定義以下:express
[DllImport("user32.dll", SetLastError = true)] private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
因爲Office應用程序是非託管程序,WPF窗體是託管程序,.NET提供了一個 WindowInteropHelper 包裝類,它能夠將一個託管程序窗體包裝獲得一個窗口句柄,以後,就能夠調用上面的Win32函數 SetParent 設置窗口的父子關係了。框架
下面方法是一個完整的方法,能夠經過反射實例化一個WPF窗體對象,而後設置此WPF窗體對象爲Office應用程序的子窗體,並正常顯示在Office應用程序上。函數
/// <summary> /// 在Excle窗口上顯示WPF窗體 /// </summary> /// <param name="assemplyName">窗體對象所在程序集</param> /// <param name="paramClassFullName">窗體對象全名稱</param> public static void ExcelShowWPFWindow(string assemplyName, string paramClassFullName) { Application.Current.Dispatcher.Invoke(new Action(() => { try { Assembly assembly = Assembly.Load(assemplyName); Type classType = assembly.GetType(paramClassFullName); object[] constuctParms = new object[] { }; dynamic view = Activator.CreateInstance(classType, constuctParms); Window winBox = view as Window; var winBoxIntreop = new WindowInteropHelper(winBox); winBoxIntreop.EnsureHandle(); //將Excel句柄指定爲當前窗體的父窗體的句柄,參考 https://blog.csdn.net/pengcwl/article/details/7817111 //ExcelApp 是一個Excle應用程序對象 var excelHwnd = new IntPtr(OfficeApp.ExcelApp.Hwnd); winBoxIntreop.Owner = excelHwnd; SetParent(winBoxIntreop.Handle, excelHwnd); winBox.ShowDialog(); } catch (Exception ex) { MessageBox.Show("打開窗口錯誤:"+ex.Message); } })); } }
下面是打開的效果圖:測試
不過,既然是的打開了一個模態窗口,咱們固然是想得到窗口的返回值。在WinForms比較簡單,可是在WPF就須要作下設置。spa
首先看到上圖的WPF窗體的XAML定義:.net
<Window x:Class="MyWPF.View.Test" 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:MyWPF.View" DataContext="{Binding TestViewModel, Source={StaticResource MyViewModelLocator}}" mc:Ignorable="d" Title="Test" Height="300" Width="300"> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition Height="80"/> </Grid.RowDefinitions> <TextBox Text="{Binding TestVale1}"/> <Button Content="sure" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="TestBtn" Click="testBtn_Click"/> </Grid> </Window>
窗體綁定了一個 TestViewModel1的ViewModel:線程
public class TestViewModel : EntityBase,IWindowReturnValue<string> { public TestViewModel() { } public string TestValue1 { get { return getProperty<string>("TestValue1"); } set { setProperty("TestValue1",value,1000); ReturnValue = value; } } public string ReturnValue { get; set; } public string BackTest() { return TestValue1; } } }
TestViewModel 繼承了SOD框架的實體類基類,它能夠方便的實現MVVM的依賴屬性,參考SOD的MVVM實現原理。本文重點看IWindowReturnValue<T>接口的定義:excel
public interface IWindowReturnValue<T> { T ReturnValue { get; set; } }
接口很簡單,就是定義一個返回值屬性,這個屬性在ViewModel 裏面適當的時候給它賦值便可。code
最後,咱們改寫下前面的Excle打開窗體的函數就能夠了,代碼以下:
public static T ExcelShowWPFWindow<T>(string assemplyName, string paramClassFullName) { T result = default(T); Application.Current.Dispatcher.Invoke(new Action(() => { try { Assembly assembly = Assembly.Load(assemplyName);
Type classType = assembly.GetType(paramClassFullName);
object[] constuctParms = new object[] { };
dynamic view = Activator.CreateInstance(classType, constuctParms);
Window winBox = view as Window;
var winBoxIntreop = new WindowInteropHelper(winBox);
winBoxIntreop.EnsureHandle();
//將Excel句柄指定爲當前窗體的父窗體的句柄,參考 https://blog.csdn.net/pengcwl/article/details/7817111 var excelHwnd = new IntPtr(OfficeApp.ExcelApp.Hwnd);
winBoxIntreop.Owner = excelHwnd;
SetParent(winBoxIntreop.Handle, excelHwnd); var dataModel = winBox.DataContext as IWindowReturnValue<T>; winBox.ShowDialog(); result = dataModel.ReturnValue; } catch (Exception ex) { MessageBox.Show("打開窗口錯誤:" + ex.Message); } })); return result; } }
最後運行此示例,測試經過。
注意:
有時候因爲某些緣由,打開的Excle或者Word窗口會跑到主程序後面去,這個時候關閉咱們上面的WPF模態窗口後,就看不到Excel窗口了,這樣用戶體驗不太好。可使用Win32的方法強行將Excel窗口再顯示在前面來,用下面這個方法:
[DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd);
其中 hWnd就是Excle的句柄。
另外還有一個問題,當用戶切換到其它進程,離開這個WPF模態子窗體,好比桌面,再切換回來,發現子窗體出不來,EXCEL彈出來一個警告對話框,內容大概是下面的樣子:
Microsoft Excel 正在等待其餘應用程序以完成對象連接與嵌入操做。
關閉這個對話框,要切換到WPF模態對話框也難以切換回來,讓人很懊惱,軟件無法使用了。
這個時候只須要關閉警告便可,等WPF子窗體操做完,再開啓警告。
excelApplication.DisplayAlerts = false;