在.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,);
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;

在Office应用中打开WPF窗体并且让子窗体显示在Office应用上的更多相关文章

  1. 一张图搞定OAuth2.0 在Office应用中打开WPF窗体并且让子窗体显示在Office应用上 彻底关闭Excle进程的几个方法 (七)Net Core项目使用Controller之二

    一张图搞定OAuth2.0   目录 1.引言 2.OAuth2.0是什么 3.OAuth2.0怎么写 回到顶部 1.引言 本篇文章是介绍OAuth2.0中最经典最常用的一种授权模式:授权码模式 非常 ...

  2. Delphi中,除了应用程序主窗口会显示在任务栏上,其它窗口默认都不会显示在任务栏.

    Delphi中,除了应用程序主窗口会显示在任务栏上,其它窗口默认都不会显示在任务栏. Delphi中,除了应用程序主窗口会显示在任务栏上,其它窗口默认都不会显示在任务栏.没有MS开发环境中的ShowI ...

  3. eclipse中打开含有汉字的properties文件显示乱码

    http://blog.csdn.net/wangjun5159/article/details/46965831

  4. 关于WinForm引用WPF窗体---在Winform窗体中使用WPF控件

    项目中有个界面展示用WPF实现起来比较简单,并且能提供更酷炫的效果,但是在WinForm中使用WPF窗体出现了问题,在网上找了一下有些人说Winform不能引用WPF的窗体,我就很纳闷,Win32都能 ...

  5. 如何安装 Microsoft Office 兼容包,以便您可以在早期版本的 Microsoft Office 中打开和保存 Office Open XML 格式

    https://support.microsoft.com/zh-cn/kb/923505 针对 Office 2003 的支持已终止 Microsoft 已于 2014 年 4 月 8 日终止了针对 ...

  6. 在Winform窗体中使用WPF控件(附源码)

    原文:在Winform窗体中使用WPF控件(附源码) 今天是礼拜6,下雨,没有外出,闲暇就写一篇博文讲下如何在Winform中使用WPF控件.原有是我在百度上搜索相关信息无果,遂干脆动手自己实现. W ...

  7. FORM实现中打开图片,链接,文档(参考自itpub上一篇帖子,整理而来)

    FORM实现中打开图片,链接,文档 参考自itpub上一篇帖子,整理而来 1.添加PL程序库D2kwutil.pll 2.主要实现程序 /*过程参数说明: v_application --打开文件的应 ...

  8. 在WPF程序中打开网页:使用代理服务器并可进行JS交互

    本项目环境:使用VS2010(C#)编写的WPF程序,通过CefSharp在程序的窗体中打开网页.需要能够实现网页后台JS代码中调用的方法,从网页接收数据,并能返回数据给网页.运行程序的电脑不允许上网 ...

  9. winfrom窗体中嵌套WPF控件

    前言 本文主要介绍如何在winfrom窗体中嵌套WPF控件, 一来是自己记录一下,而来希望能对有需要的朋友提供实现思路. 如有错误请指出...下面进入正题... -1.前期准备 准备一个建立好的win ...

随机推荐

  1. 【转载】看StackOverflow如何用25台服务器撑起5.6亿的月PV

    问答社区网络 StackExchange 由 100 多个网站构成,其中包括了 Alexa 排名第 54 的 StackOverflow.StackExchang 有 400 万用户,每月 5.6 亿 ...

  2. Redis sortedset实现元素自动过期

    这里的自动过期,Redis并没有提供相应的api,但是可以使用一下方法来实现. 需求背景: 给用户返回的文章要求七日内不能重复:文章是存放在java list里边:(这一块就是从db将文章拿出来,然后 ...

  3. JNI实战(一):JNI HelloWorld

    使用最新Android Studio的Cmake,创建一个Native C++项目后,我们就可以看到JNI的Hello World的项目及示例代码了. JNI的项目代码,分为三层:Java层,C++层 ...

  4. 第2章 Java编程基础

    本章重点 ·Java的基本语法形式 ·Java语言中的常量与变量 ·Java语言运算符的使用 ·Java程序的流程控制 ·Java中方法的定义与使用 ·Java中数组的定义与使用 2.1 Java的基 ...

  5. 【Spark篇】---SparkSQL on Hive的配置和使用

    一.前述 Spark on Hive: Hive只作为储存角色,Spark负责sql解析优化,执行. 二.具体配置 1.在Spark客户端配置Hive On Spark 在Spark客户端安装包下sp ...

  6. 深入理解OkHttp源码(三)——网络操作

    这篇博客侧重于了解OkHttp的网络部分,包括Socket的创建.连接,连接池等要点.OkHttp对Socket的流操作使用了Okio进行了封装,本篇博客不做介绍,想了解的朋友可以参考拆轮子系列:拆O ...

  7. 了解 HTTPS,读这篇文章就够了

    今天接到个活儿,让我科普 HTTPS .讲 HTTP 我都“方”,想要通俗易懂的说完 HTTPS, 我有点“圆”.在讲什么是 HTTPS 之前,我们先来看个漫画.   △ 图片来源于阮一峰的网络日志 ...

  8. 基于 dubbo 的分布式架构

    前言 现在越来越多的互联网公司还是将自己公司的项目进行服务化,这确实是今后项目开发的一个趋势,就这个点再凭借之前的 SSM 项目来让第一次接触的同学能快速上手. 浅谈分布式架构 分布式架构单看这个名字 ...

  9. 《HelloGitHub月刊》第 05 期

    <HelloGitHub>第 05 期 兴趣是最好的老师,<HelloGitHub>就是帮你找到兴趣! 欢迎各路人士加入本项目,丰富月刊的内容,也可以直接在Issue(需要登录 ...

  10. React Native (二) ios打包到真机

    每当在模拟器上完成了开发,都想到真机上试试,正好前段时候淘了一个imac. 这里就以打包rndemo到iphone为例,讲一下react ntive ios打包到真机的流程. 一.前置 1.有个iph ...