在Office應用中打開WPF窗體而且讓子窗體顯示在Office應用上

在.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;
相關文章
相關標籤/搜索