关于本站
1、基于Django+Bootstrap开发
2、主要发表本人的技术原创博客
3、本站于 2015-12-01 开始建站
昨天写了一篇博文:VSTO窗体在Excel内弹窗置顶。
既然Excel可以处理该功能需求。那么在Word中,是否也可以完成一样的功能?
按照在Excel中的方法,插入ClsWinWrap类的代码。
using System; using System.Windows.Forms; //命名空间自行修改 namespace WordAddin1 { /// <summary> /// IWin32Window接口类,为了实现弹出的窗体在Excel内 /// </summary> public class ClsWinWrap:IWin32Window { private IntPtr m_Handle; //构造函数,参数是父窗口的句柄 public ClsWinWrap(IntPtr handle) { this.m_Handle = handle; } //构造函数,参数是父窗口的句柄 public ClsWinWrap(int handle) { this.m_Handle = new IntPtr(handle); } public IntPtr Handle { get { return m_Handle; } } //打开窗体,Show参数使用该类自身 public void Show(Form frm) { frm.Show(this); } //模式打开窗体,Show参数使用该类自身 public DialogResult ShowDialog(Form frm) { return frm.ShowDialog(this); } } }
下一步,获取Word Application的句柄。
发现Word Application没有类似Excel.Application.Hwnd的属性。
出师不利啊,既然没有提供属性直接获取Hwnd属性。那么,我们自己写代码获取句柄。
获取句柄可以利用windows的api函数FindWindow获取。
FindWindow需要窗体类名或者窗体标题即可获取窗体的句柄。
那么我们怎么知道Word Application的窗体类名?
可以使用微软的Spy++(自己电脑没有可以自行搜索)。打开一个Word文档和Spy++。
Spy++工具栏上有个查找窗口的按钮。打开,鼠标移动到查找程序工具的图标上。点击鼠标不松开。
再把鼠标移动到Word文档的标题上。
此时可以看到显示相关的句柄和类名信息。松开鼠标,再点击确定按钮。
该句柄不是我们真正要查找的句柄。可以再点击“同步”按钮,定位到Spy++监控的具体位置。
我们一开始查找到的句柄是Ribbon菜单所在容器的句柄。可以通过结构树,看到最顶层的Application。
其中,"文档1 - Microsoft Word"是窗体标题。"OpusApp"是我们要查找的类名。
回到VSTO,继续写让窗口在Word内部打开并内部置顶的代码。
新建一个静态类,获取Word Application的句柄代码如下:
using System; using System.Runtime.InteropServices; using Word = Microsoft.Office.Interop.Word; //命名空间自行修改 namespace WordAddin1 { /// <summary> /// 获取Office对象 /// </summary> static class ClsOfficeObject { [DllImport("user32.dll", EntryPoint = "FindWindow")] private static extern int FindWindow(string lpClassName, string lpWindowName); public static Word.Application Application { get { return Globals.ThisAddIn.Application; } } /// <summary> /// 获取当前Word的句柄 /// </summary> public static int Hwnd { get { return FindWindow("OpusApp", null); } } } }
那么,在Word内部打开窗体的代码如下:
ClsWinWrap clsWin = new ClsWinWrap(ClsOfficeObject.Hwnd); FormTest frm = new FormTest(); clsWin.Show(frm); //打开窗口,指定它的父亲是Word
再设置窗体的相关属性,可以实现Word内部打开窗体。
但,真的这么简单就搞定了吗?如果这么简单的话,我就不用再写一篇文章。
再打开一个Word文档,同时打开两个。点击打开测试窗体只会在其中一个文档中置顶,和另外一个文档不关联。
另外一个文档会遮住该窗体。
我用Spy++定位找到另外一个文档。如下图:
发现OpusApp不唯一。也就是说Word程序结构和Excel的不同。难怪Word Application没有Hwnd属性。
后来,发现设置窗体的所属只能在Show方法设置。该方法若窗体已经打开了,则不能再执行。
我们可以再切换Word文档的事件控制。将所有打开的窗体Hide隐藏。再重新Show显示出来即可。
using System; using System.Windows.Forms; using System.Reflection; using System.Runtime.InteropServices; using Word = Microsoft.Office.Interop.Word; //命名空间自行修改 namespace WordAddin1 { /// <summary> /// 窗体管理类,需要在Global级实例化对象(因为需要绑定事件) /// </summary> public class ClsFormManage { private Dictionary<Type, Form> FormConllent = new Dictionary<Type, Form>(); /// <summary> /// 判断是否已经包含该窗体的实例了 /// </summary> public bool ContainForm(Type type) { return this.FormConllent.ContainsKey(type); } /// <summary> /// 根据窗体类,获取一个实例 /// </summary> /// <param name="type">窗体类类型</param> /// <param name="args">构造函数需要的参数</param> /// <returns></returns> public Form GetInstanceForm(Type type, object[] args) { Form frm; if (!this.FormConllent.ContainsKey(type)) { //实例化对象 List<Type> types = new List<Type>(); foreach (var item in args) { types.Add(item.GetType()); } ConstructorInfo cti = type.GetConstructor(types.ToArray()); frm = (Form)cti.Invoke(args); this.FormConllent.Add(type, frm); //显示窗体 ClsWinWrap clsWin = new ClsWinWrap(ClsOfficeObject.Hwnd); clsWin.Show(frm); frm.FormClosing += frm_FormClosing; //绑定窗体关闭事件 } else { frm = this.FormConllent[type]; frm.Activate(); } return frm; } void frm_FormClosing(object sender, FormClosingEventArgs e) { //移除窗体 Form frm = (Form)sender; Type type = frm.GetType(); if (this.FormConllent.ContainsKey(type)) { this.FormConllent.Remove(type); } } public ClsFormManage() { //绑定文档激活事件 Globals.ThisAddIn.Application.WindowActivate += Application_WindowActivate; } //激活文档(遍历句柄,设置窗体归属) void Application_WindowActivate(Word.Document Doc, Word.Window Wn) { ClsWinWrap clsWinWrap = new ClsWinWrap(ClsOfficeObject.Hwnd); Word.Application app = ClsOfficeObject.Application; app.ScreenUpdating = false; try { foreach (Form item in FormConllent.Values) { item.Hide(); item.Show(clsWinWrap); } } catch (Exception ex) { MessageBox.Show(ex.Message, "Error"); } app.ScreenUpdating = true; } } }
接着,打开ThisAddin.cs文件。加入如下代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Linq; using Word = Microsoft.Office.Interop.Word; using Office = Microsoft.Office.Core; //命名空间自行修改 namespace WordAddin1 { public partial class ThisAddIn { /// <summary> /// 窗口管理 /// </summary> public ClsFormManage FormManage; private void ThisAddIn_Startup(object sender, System.EventArgs e) { FormManage = new ClsFormManage(); } }
将FormManage定位为全局的变量。
那么我们打开窗体的代码改成如下:
ClsFormManage clsFormManage = Globals.ThisAddIn.FormManage; FormTest frm = clsFormManage.GetInstanceForm(typeof(FormTest), new object[] { });
使用ClsFormManage的GetInstanceForm获取一个示例。
其中第2个参数是写FormTest初始化的参数,若没有需要传递的参数写 new object[]{} 即可。
该方法使用到反射机制。是不是很复杂啊,晕了正常。跟着代码练习一下就容易读懂。
相关专题: VSTO的那些坑