我的VSTO之路(三):Word基本知识

前一篇文章中,我初步介绍了如何如何开发一个VSTO程序,在本文中,我将进一步深入介绍Word的插件开发。Word是一个大家在日常工作中一直接触的文档工具,也是微软最赚钱的产品之一。从最初的Word 1.0到现在的Word 2010历经了13代的演化,已经成为了一个比较复杂的系统。(这里稍微跑题一下,Office 2010的版本代号是version 14,但是我为什么说Word一共演化了13代呢?因为Office并没有Version 13,上一代的Office 2007是Version 12,微软觉得13这个死数字不吉利,所以直接跳过了……Oh my god眨眼)。言归正传,我录制了一段视频来演示本文所要介绍的内容。

这段视频,描述了一个简单的Word搜索插件,包含了以下几个功能点

  1. 自定义Ribbon
  2. 自定义Task Pane
  3. VSTO插件中获取Word内容全文
  4. 修改Word内容和样式

其中关于如何创建Ribbon和Task Pane的内容,我已经在前一篇文章中介绍了,如果你还不熟悉,可以看这里

 

Word Object Model 介绍

首先,要开发出良好的程序,我们需要了解我们的开发平台,而Word本身是一个很复查的平台,我在这里先从Word的对象模型开始介绍。Word Object Model中一共包含有数百个不同类型的对象,其中最关键的也是最常用的是Application、Document、Range、Selection和Bookmark,他们的关系如下图:

Word Object Model Abstract

我来依次介绍这几个对象:

Application 对象

Application代表Word程序,而一个Word程序内可以包含多个Word文档。用通俗的话来说,无论你开几个Word文档,都是在一个Word进程里面管理。这我们以后会讲到的Excel不一样。同时Application又是所有Word对象根,你可以通过Application对象,获得其他对象。在Addin开发过程中,我们可以通过以下方式来获得Application对象:

    Globals.ThisAddIn.Application
Document 对象

Document对象代表着一个Word文档,即便你刚打开你的Word,是一个空的新文档,也会有一个Document。在开发过程中,以下这个属性从Application中获得当前的Document对象:

    Globals.ThisAddIn.Application.ActiveDocument

此外Application对象也维护着一个集合,即Application.Documents,里面包含着现在所有打开的Word文档。

 

Range 对象

Range是一个比较特殊的对象(我其实都不知道如何用中文翻译贴切地翻译这个单词),在你日常使用Word的过程中,你甚至可能不会知道有这样一个对象存在,但如果你想通过程序方式修改Word正文的内容,Range是一个很关键的对象。用微软官方的表述,Range代表着文档中一片连续的区域,微软为它列出了一下几个特性:

  1. Range的组成成分可以是单独的插入点,也可以是一个文本范围或整个文档。
  2. Range包含非打印字符,例如空格、制表符和段落标记。
  3. Range可以是当前所选内容所表示的区域,也可以表示当前所选内容之外的区域。
  4. Range与始终可见的所选内容不同,它在文档中是不可见的。
  5. Range不随文档保存,仅存在于代码运行期间。

我再为它加2条

  1. Range有明确的开始和结束,但不同的Range之间是可以有交集的
  2. Range的长度是在变化的,如果你往一个Range里面插入的一个单词,它的长度会自动变长。

获得Range对象的方式很多,我们可以通过Document对象的Range(ref object Start = Type.Missing, ref object End = Type.Missing)方法,创建一个自定义的Range。通过Word中文档相关的对象都有一个Range属性,比如Paragraph.Range、Selection.Range。

 

Selection 对象

Selection代表着当前光标所选中的对象,我们在开发过程中这个对象会和Application.WindowSelectionChange一起使用。

   1:          //
   2:          // Summary:
   3:          //     Occurs when the selection changes in the active document window.
   4:          event ApplicationEvents4_WindowSelectionChangeEventHandler WindowSelectionChange;

Delegate接口

   1:      [TypeLibType(16)]
   2:      [ComVisible(false)]
   3:      public delegate void ApplicationEvents4_WindowSelectionChangeEventHandler(Selection Sel);

 

Bookmark 与 Content Control

Bookmark即书签,在Word文档中做一个标记,方便查阅。开发过程中,我们可以基于Range来创建Bookmark。如:

   1:      // 将第一段文档标记为一个BookMark 
   2:      Word.Range range = Globals.ThisAddIn.Application.ActiveDocument.Paragraphs[0].Range;
   3:      range.Bookmarks.Add("JustinTest");

 

至此我们已经介绍完毕了Word中的主要5个对象,现在我们运用我们学到的东西,来实开头视频中的那个插件

 

插件:自定义Search面板

我先定义一下我们要实现的功能点,获得Word文档的内容,取得与检索关键字相关的上下文并显示在ListView中,当用户点中ListView中的项目时,高亮显示Word文档中对应的内容。以下是如何实现这几个功能的介绍。

 

获得Word的全文
要取得当前Word的全文,我们主要要解决两个问题。

  1. 如何获得当前的Document对象?
  2. 如何通过Document对象获得文档内容?

对于第一个问题,因为搜索功能主要是写在Task Pane中的UserControl中(这一点在上一篇文章中已经有过介绍),所以取得Document对象的主要方法,是通过Application对象获得ActiveDocument的对象,即当前编辑的文档:

    Globals.ThisAddIn.Application.ActiveDocument

对于第二个问题,我们有两个方法:首先,Document对象有Paragraphs集合,这个集合里面包含了每个段落的对象,而每个段落对象,都有Range属性,我们可以通过Paragraph.Range.Text,来获得每个段落的正文。其次Document对象有一个Range方法,通过它我们可以把整个Document作为一个Range。

 

Search按钮代码

   1:          private void btnSearch_Click(object sender, EventArgs e)
   2:          {
   3:              // 清楚文档中的高亮显示
   4:              ClearMark();
   5:   
   6:              lvSearchResult.Items.Clear();
   7:              if (string.IsNullOrWhiteSpace(tbSearchText.Text))
   8:              {
   9:                  return;
  10:              }
  11:   
  12:              // 按段落检索
  13:              Word.Document currentDocument = Globals.ThisAddIn.Application.ActiveDocument;
  14:              if (currentDocument.Paragraphs != null &&
  15:                  currentDocument.Paragraphs.Count != 0)
  16:              {
  17:                  foreach (Word.Paragraph paragraph in currentDocument.Paragraphs)
  18:                  {
  19:                      MatchCollection mc = Regex.Matches(paragraph.Range.Text, tbSearchText.Text.Trim(), RegexOptions.IgnoreCase);
  20:                      if (mc.Count > 0)
  21:                      {
  22:                          foreach (Match m in mc)
  23:                          {
  24:                              try
  25:                              {
  26:                                  int startIndex = paragraph.Range.Start + m.Index;
  27:                                  int endIndex = paragraph.Range.Start + m.Index + m.Length;
  28:   
  29:                                  Word.Range keywordRange = currentDocument.Range(startIndex, endIndex);
  30:                                  
  31:                                  // 获取上下文信息
  32:                                  // 获取前两个单词的位置(如果有)
  33:                                  startIndex = GetStartPositionForView(paragraph, m, startIndex);
  34:   
  35:                                  // 获取后两个单词的位置(如果有)
  36:                                  endIndex = GetEndPositionForView(paragraph, m, endIndex);
  37:   
  38:                                  // 在ListView中展示检索的关键字以及其上下文
  39:                                  Word.Range range = currentDocument.Range(startIndex, endIndex);
  40:                                  ListViewItem item = new ListViewItem(range.Text);
  41:                                  item.Tag = keywordRange;
  42:                                  lvSearchResult.Items.Add(item);
  43:                              }
  44:                              catch (Exception ex)
  45:                              {
  46:                                  MessageBox.Show(ex.Message);
  47:                              }
  48:                          }
  49:                      }
  50:                  }
  51:              }
  52:          }

介绍一下这段代码的几个功能点:

  1. 在Search功能开始时,先清除文档中的高亮显示(ClearMark方法稍后会介绍)。
  2. 分段落,依次查找关键字。
  3. 获得关键字的上下文,并放入ListView中显示。需要注意的时候,我在ListViewItem的tag对象里面,存入了Keyword在文档中的Range,为了ListView点击事件。

 

ListView点击事件

   1:          private void lvSearchResult_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
   2:          {
   3:              ClearMark();
   4:              if (lvSearchResult.SelectedItems.Count > 0)
   5:              {
   6:                  Word.Range range = lvSearchResult.SelectedItems[0].Tag as Word.Range;
   7:   
   8:                  // 为了可以恢复被修改的Range,我先将该Range和原本的Color放入Class的成员
   9:                  _LastRange = range;
  10:                  _LastRangeBackColor = range.HighlightColorIndex;
  11:                  range.HighlightColorIndex = Word.WdColorIndex.wdYellow;
  12:              }
  13:          }

总体来说这段代码很简单,我稍微介绍一下Range对象的使用,这里我修改了HighlightColorIndex属性,来修改文字的背景色,如果你修改字体相关的样式,可以通过Range.Font属性。(此外,我会在下一篇文中,介绍如何通过Range加超链接、书签或者Content Control)。

 

其他方法

   1:          private void ClearMark()
   2:          {
   3:              if (_LastRange != null)
   4:              {
   5:                  _LastRange.HighlightColorIndex = _LastRangeBackColor;
   6:              }
   7:          }

我在Search事件和ListView点击方法中都会先调用这个方法,它会使用在lvSearchResult_ItemSelectionChanged中保存的Range和Color,来恢复之前的样式。

 

总结

本文中,我介绍了Word对象模型的基本内容和我写的一个Word插件,包含对Application、Document和Range对象的操作。Word程序包含的内容是很多的,本来想把自己了解的Word知识一次写完,但写到这里发现已经写了很多了,怕大家会看得累眨眼,所以先发出来,再下一篇文章,我会进一步深入介绍Word插件开发。下次内容预告:

  1. 修改右键菜单
  2. 往文档中插入内容
  3. 添加超链接、书签
  4. 基于选中内容,显示悬浮框

 

最后,本文范例的代码可以在这里下载。此外,本文欢迎转载,但请保留出处,大家如果有问题,可以联系我 justin.tyrael@gmail.com

posted @ 2011-05-14 09:40  Justin Zhang  阅读(12952)  评论(16编辑  收藏