zoukankan      html  css  js  c++  java
  • 在Office应用中打开WPF窗体并且让子窗体显示在Office应用上

    在.NET主程序中,我们可以通过创建 ExcelApplication 对象来打开一个Excel应用程序,如果我们想在Excle里面再打开WPF窗口,问题就不那么简单了。

    我们可以简单的实例化一个WPF窗体对象然后在Office应用程序的窗体上打开这个新的WPF窗体,此时Office应用的窗体就是这个WPF的宿主窗体,这个WPF窗体是Office应用窗体的“子窗体”。然后子窗体跟宿主不是在一个UI线程上,也不在同一个进程上,子窗体很可能会在宿主窗体后面看不到。这个时候需要调用Win32函数,将Office应用的窗体设置为WPF子窗体的父窗体,让WPF子窗体成为真正的“子窗体”。这个函数的形式定义如下:

    [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就需要做下设置。

    首先看到上图的WPF窗体的XAML定义:

    <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>接口的定义:

      public interface IWindowReturnValue<T>
        {
            T ReturnValue { get; set; }
        }

    接口很简单,就是定义一个返回值属性,这个属性在ViewModel 里面适当的时候给它赋值即可。

    最后,我们改写下前面的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;
  • 相关阅读:
    Servlet学习(三)——实例:用户登录并记录登陆次数
    Servlet学习(二)——ServletContext对象
    Servlet学习(一)——Servlet的生命周期、执行过程、配置
    Tomcat学习(一)——使用Eclipse绑定Tomcat并发布应用
    Http请求和响应
    MySQL学习(六)——自定义连接池
    MySQL学习(五)——使用JDBC完成用户表CRUD的操作
    SQLServer -------- 连接失败 错误代码126
    java ------ I/O (四) 读写文本文件
    C# ------- 二维表变成一行数据存储,使用后如何分别获取
  • 原文地址:https://www.cnblogs.com/bluedoctor/p/8946215.html
Copyright © 2011-2022 走看看