当前位置: 首页 > news >正文

在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;

 

相关文章:

  • 分享一款本地音乐播放器源码
  • Swoft 源码剖析 - 代码自动更新机制
  • 深度学习(五)基于tensorflow实现简单卷积神经网络Lenet5
  • 移动端开发干货
  • Qcon 演讲纪实:详解如何在实时视频通话中实现AR功能
  • 大咖 | 卡耐基梅隆教授Tom Mitchell:人工智能在中国前景光明,有2点要注意
  • 注释那些事儿:前端代码质量系列文章(一)
  • 专访腾讯云沙开波:从无到有,打造全球领先调度系统
  • 巨杉数据库:金融级数据库未来方向
  • Python之md5.update才过的哪些坑
  • IIS7/7.5/8如何实现访问HTTP跳转到HTTPS访问
  • overflow问题--滚动设置?
  • Apache Curator操作zookeeper的API使用
  • AutoEx应用崩溃自动匹配Stack Overflow的解答
  • Hessian入门
  • “寒冬”下的金三银四跳槽季来了,帮你客观分析一下局面
  • Brief introduction of how to 'Call, Apply and Bind'
  • JS变量作用域
  • js递归,无限分级树形折叠菜单
  • Magento 1.x 中文订单打印乱码
  • MYSQL如何对数据进行自动化升级--以如果某数据表存在并且某字段不存在时则执行更新操作为例...
  • SpriteKit 技巧之添加背景图片
  • 大整数乘法-表格法
  • ------- 计算机网络基础
  • 简单易用的leetcode开发测试工具(npm)
  • 名企6年Java程序员的工作总结,写给在迷茫中的你!
  • 漂亮刷新控件-iOS
  • 少走弯路,给Java 1~5 年程序员的建议
  • 使用parted解决大于2T的磁盘分区
  • 栈实现走出迷宫(C++)
  • MyCAT水平分库
  • Nginx惊现漏洞 百万网站面临“拖库”风险
  • 阿里云ACE认证学习知识点梳理
  • ​html.parser --- 简单的 HTML 和 XHTML 解析器​
  • ​queue --- 一个同步的队列类​
  • ​软考-高级-系统架构设计师教程(清华第2版)【第12章 信息系统架构设计理论与实践(P420~465)-思维导图】​
  • #pragma pack(1)
  • #Spring-boot高级
  • #vue3 实现前端下载excel文件模板功能
  • #我与Java虚拟机的故事#连载08:书读百遍其义自见
  • (20)目标检测算法之YOLOv5计算预选框、详解anchor计算
  • (C++17) optional的使用
  • (Demo分享)利用原生JavaScript-随机数-实现做一个烟花案例
  • (附源码)ssm考试题库管理系统 毕业设计 069043
  • (三) prometheus + grafana + alertmanager 配置Redis监控
  • (三)docker:Dockerfile构建容器运行jar包
  • (四)c52学习之旅-流水LED灯
  • (一)UDP基本编程步骤
  • (转)Android中使用ormlite实现持久化(一)--HelloOrmLite
  • (轉貼) 寄發紅帖基本原則(教育部禮儀司頒布) (雜項)
  • **Java有哪些悲观锁的实现_乐观锁、悲观锁、Redis分布式锁和Zookeeper分布式锁的实现以及流程原理...
  • ... fatal error LINK1120:1个无法解析的外部命令 的解决办法
  • .NET 使用 ILMerge 合并多个程序集,避免引入额外的依赖
  • .NET高级面试指南专题十一【 设计模式介绍,为什么要用设计模式】
  • .NET设计模式(8):适配器模式(Adapter Pattern)