原文:我的VSTO之路(五):Outlook初步开发之联系人扩展

上一讲我们完成对Word的介绍,文本开始,我将着重介绍Outlook。Outlook是微软Office中一个非常实用的工具,尤其在一个拥有Windows Domain的公司局域网中,Outlook是员工最常用的通讯工具,所以对Outlook实行进一步的定制开发的需求量是很大的。本文中,我先以联系人的扩展为开始,讲解如何开发一个强大的Outlook插件。

故事的开始

首先我们假设一个场景。有一天,市场部的同事来找你帮个小忙(有经验的人都知道,一般这都是无尽痛苦的开始)。他们希望在Outlook的联系人信息上加入对方父母生日信息,方便他们做市场营销(嘿嘿,怎么营销大家自己想咯)。同时,最好能够和Outlook原来的联系人界面保持一致便于查阅。为了不辜负同事的期望,你打开了Visual Studio……

简单思索过后,你在脑海中浮现了一下两个功能点:

  1. Outlook Form Region:用于制作界面,可以和原本的Outlook界面整合在一起。
  2. Contact User Define Fields:用于保存新加入的属性。

而新的联系人界面,你打算画成这样:

业务逻辑:每次Contact界面展示的时候,我们制作的Form Region会读联系人中用户自定义属性里面的值(我们分别为父母的名字和生日创建4个用户属性),并展示在界面上。当Contact关闭的时候,如果用户改变了这四个控件的值,我们将其写回到自定义属性中去。关于用户自定义的属性,如下图:

Outlook Object Model介绍

正式开始之前,我们先了解一下Outlook的对象模型。

Microsoft.Office.Interop.Outlook.Application

Outlook中的Application对象就像我在之前在Word中介绍过的Application对象一样,是所有对象的最顶层。即便你同时开多个Outlook,你也只创建了一个Application(进程也只有一个)。Application对象中,我们需要重点关注三个地方:

  1. CreateItem方法:通过这个方法,我们创建新的Outlook对象,例如Email、Task、Contact等。
  2. Explorers属性: 获得当前的Outlook.Explorer对象集合。
  3. Inspectors属性: 获得当前的Outlook.Inspector对象集合。

有很多方法可以获得Application对象,比较常用的是直接在Addin工程中,通过Globals.ThisAddIn.Application获得。如果你看过我之前写的Word插件,你会发现这里是和Word VSTO,其实这是微软为我们设定的模式。包括在之后的Excel和PowerPoint中也都是这样的。我这里进一步介绍一下Globals,它是一个在ThisAddIn.Designer.cs中定义的一个类,对于Outlook来说,它定义了三个静态属性,ThisAddin、Ribbons和Inspectors对象。Globals作用是,通过它我们可以在Addin项目的任何位置获得我们所需要的上下文。

Microsoft.Office.Interop.Outlook.Explorer

Explorer类,即当前Outlook的主窗口。示显示包含项(如电子邮件、任务或约会)的文件夹内容的窗口。Explorer 类包括可用来修改窗口的方法和属性,以及窗口更改时所引发的事件。需要注意的是,Outlook是可以开多个Explorer,但是只有一个Application。这点和Word类似,而和我们以后会讲到的Excel不同。一般我们通过Globals.ThisAddIn.Application.ActiveExplorer()方法来获得当前(焦点所在)的Explorer。

Microsoft.Office.Interop.Outlook.Inspector

Inspector类,即一个Outlook Item的窗口,比如你写邮件时弹出的窗口就是一个Inspector,你创建一个新的联系人也是一个Inspector。Inspector类是在开发Outlook插件中很常用的。尤其是以下几个地方:

  1. 获得当前的Inspector:Globals.ThisAddIn.Application.ActiveInspector(),很多时候我们都会通过这个方法获得现在的Inspector,但是这个不一定靠的住,我在今天的例子中就会讲到一种特殊情况。
  2. 新Inspector创建事件:Globals.ThisAddIn.Application.Inspectors.NewInspector,如果你需要为每个Email加一个Task Pane,就需要使用到这个事件。
  3. 获得Inspector对应的Outlook对象:Inspector.CurrentItem,Inspector只是一个窗口,而这个窗口背后的Outlook对象,则需要通过这个方法取得。

Microsoft.Office.Interop.Outlook.MAPIFolder

MAPIFolder即Outlook中的目录,Outlook中提供了16种内建的目录类型,由枚举Microsoft.Office.Interop.Outlook.OlDefaultFolders定义。

Microsoft.Office.Interop.Outlook.MailItem

Microsoft.Office.Interop.Outlook.AppointmentItem

Microsoft.Office.Interop.Outlook.TaskItem

Microsoft.Office.Interop.Outlook.ContactItem

分别对应Outlook中的Email、MeetingRequest、Task和Contact,它们都可以通过Application对象的CreateItem方法来创建。

Outlook Form Region

介绍

为了更好地制作界面,我们使用的是Outlook Form Region。它是从Outlook 2007开始,微软进入一项新的技术。它提供一种更加方便的方式扩展Outlook项目的界面。相对之前的Custom Form,Form Region是基于.net,开发更加容易,而且和Outlook本身,结合得更加紧密。

创建Form Region

首先,我们添加一个新的Item,选择Office项目类型中的Outlook Form Region

我们选择设计一个新的Form Region

Form Region的类型为Adjoining,即增加在页面的底部。

如果你需要在Region Form上添加比较多的控件,你也可以选择Separate,这样你就会有一个完整的空Form。它成为一个独立的标签页,不合原始的Form冲突。

为Form Region 起名字,并且设定在编辑模式和阅读模式中都显示我们的Region Form。

我们的Form Region是嵌入在Contact中的。

完成了这一系列的配置之后,VS会帮我们创建一个空的Form Region,如果需要修改刚才的设置,可以它在属性中修改,如下图。

我们开始往这个Form上添加控件,一共两个textbox、两个datetimepicker:

业务逻辑实现

流程图

这是根据我们的业务逻辑代码制作的流程图。

源代码分析

成员变量

 1:  // 自定义的属性名字
 2:  private const string PROPERTY_NAME_MOTHER_NAME = "PROPERTY NAME MOTHER NAME";
 3:  private const string PROPERTY_NAME_MOTHER_BIRTHDAY = "PROPERTY NAME MOTHER BIRTHDAY";
 4:  private const string PROPERTY_NAME_FATHER_NAME = "PROPERTY NAME FATHER NAME";
 5:  private const string PROPERTY_NAME_FATHER_BIRTHDAY = "PROPERTY NAME FATHER BIRTHDAY";
 6:  
 7:  // 自定义属性对象
 8:  private Outlook.UserProperty _MotherNameProperty = null;
 9:  private Outlook.UserProperty _MotherBirthdayProperty = null;
 10:  private Outlook.UserProperty _FatherNameProperty = null;
 11:  private Outlook.UserProperty _FatherBirthdayProperty = null;
 12:  
 13:  // 对应的Contact对象
 14:  public Outlook.ContactItem _Contact = null;
 15:  
 16:  // 标记是否内容修改
 17:  private bool _Changed = false;

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

FormRegionShowing事件,这里我们从Contact的用户自定义属性中取得值,并赋值给控件。如果这些属性不存在,我们则创建它们。同时我们所有的控件都绑定了Changed事件,便于我们判断值是否改变。这里有一个需要注意的地方,这里我需要获得Contact对象才能读取Property,但是如果你的Form Region是Adjoining模式的,在FormRegionShowing事件中,你会发现你通过Globals.ThisAddIn.Application.ActiveInspector()是不能取到Inspector的,进一步你也不能通过Inspector获得CurrentItem。这个问题在微软的论坛上都很少被提及。我这里所采用的方式是((EmployeeFamilyForm)sender).OutlookItem。可以看如下的代码:

 1:  private void EmployeeFamilyForm_FormRegionShowing(object sender, System.EventArgs e)
 2:  {
 3:  // 获得FormRegion所对应的Contact对象
 4:  _Contact = ((EmployeeFamilyForm)sender).OutlookItem as Outlook.ContactItem;
 5:  
 6:  // 从联系人的自定义属性中,获得母亲姓名属性
 7:  _MotherNameProperty = _Contact.UserProperties.Find(PROPERTY_NAME_MOTHER_NAME, Type.Missing);
 8:  if (_MotherNameProperty != null)
 9:  {
 10:  // 如果存在这个属性,则取出Value为控件赋值
 11:  tbMotherName.Text = _MotherNameProperty.Value as String;
 12:  }
 13:  else
 14:  {
 15:  // 不存在则创建这个属性
 16:  _MotherNameProperty = _Contact.UserProperties.Add(PROPERTY_NAME_MOTHER_NAME, Outlook.OlUserPropertyType.olText, Type.Missing, Type.Missing);
 17:  }
 18:  
 19:  // 母亲生日,原理相同
 20:  _MotherBirthdayProperty = _Contact.UserProperties.Find(PROPERTY_NAME_MOTHER_BIRTHDAY, Type.Missing);
 21:  if (_MotherBirthdayProperty != null)
 22:  {
 23:  dtpMotherBirthday.Value = (DateTime)_MotherBirthdayProperty.Value;
 24:  }
 25:  else
 26:  {
 27:  _MotherBirthdayProperty = _Contact.UserProperties.Add(PROPERTY_NAME_MOTHER_BIRTHDAY, Outlook.OlUserPropertyType.olDateTime, Type.Missing, Type.Missing);
 28:  }
 29:  
 30:  // 父亲姓名
 31:  _FatherNameProperty = _Contact.UserProperties.Find(PROPERTY_NAME_FATHER_NAME, Type.Missing);
 32:  if (_FatherNameProperty != null)
 33:  {
 34:  tbFatherName.Text = _FatherNameProperty.Value as String;
 35:  }
 36:  else
 37:  {
 38:  _FatherNameProperty = _Contact.UserProperties.Add(PROPERTY_NAME_FATHER_NAME, Outlook.OlUserPropertyType.olText, Type.Missing, Type.Missing);
 39:  }
 40:  
 41:  // 父亲生日
 42:  _FatherBirthdayProperty = _Contact.UserProperties.Find(PROPERTY_NAME_FATHER_BIRTHDAY, Type.Missing);
 43:  if (_FatherBirthdayProperty != null)
 44:  {
 45:  dtpFatherBirthday.Value = (DateTime)_FatherBirthdayProperty.Value;
 46:  }
 47:  else
 48:  {
 49:  _FatherBirthdayProperty = _Contact.UserProperties.Add(PROPERTY_NAME_FATHER_BIRTHDAY, Outlook.OlUserPropertyType.olDateTime, Type.Missing, Type.Missing);
 50:  }
 51:  
 52:  // 将这四个控件绑定change事件,只有在修改之后,我们才会将值回写到Contact对应的属性中去
 53:  tbMotherName.TextChanged += new EventHandler(content_Changed);
 54:  dtpMotherBirthday.ValueChanged += new EventHandler(content_Changed);
 55:  tbFatherName.TextChanged += new EventHandler(content_Changed);
 56:  dtpFatherBirthday.ValueChanged += new EventHandler(content_Changed);
 57:  
 58:  // 在Write事件中,把修改的值保存到属性中去
 59:  _Contact.Write += new Microsoft.Office.Interop.Outlook.ItemEvents_10_WriteEventHandler(contact_Write);
 60:  }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

content_Changed和contact_Write事件

 1:  void content_Changed(object sender, EventArgs e)
 2:  {
 3:  // 有修改时,将_Change置为true
 4:  _Changed = true;
 5:  }
 6:  
 7:  void contact_Write(ref bool Cancel)
 8:  {
 9:  if (_Changed)
 10:  {
 11:  // 保存值到属性中去
 12:  _MotherNameProperty.Value = tbMotherName.Text.Trim();
 13:  _MotherBirthdayProperty.Value = dtpMotherBirthday.Value;
 14:  _FatherNameProperty.Value = tbFatherName.Text.Trim();
 15:  _FatherBirthdayProperty.Value = dtpFatherBirthday.Value;
 16:  }
 17:  
 18:  }

EmployeeFamilyForm_FormRegionClosed事件

 1:  private void EmployeeFamilyForm_FormRegionClosed(object sender, System.EventArgs e)
 2:  {
 3:  // 关闭事件绑定
 4:  _Contact.Write -= new Microsoft.Office.Interop.Outlook.ItemEvents_10_WriteEventHandler(contact_Write);
 5:  
 6:  // 释放对象
 7:  System.Runtime.InteropServices.Marshal.ReleaseComObject(_Contact);
 8:  _Contact = null;
 9:  }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

后记

自此我们已经完成了对Outlook联系人的扩展,在本文中我们介绍了Outlook Form Region和UserProperties的使用,基本圆满完成了市场部的需求。话说IT帮他们实现这个需求之后,过了几天市场部的同事又来他们了。因为客户的人数比较多,最好能做一个查询工具希望可以做一个查询工具,来方便他们检索内容。至于如何检索Outlook内部对象的方法,且听下回分解。

最后,本文欢迎转载,但请保留出处,大家如果有问题,可以联系我 justin.tyrael@gmail.com或者到VSTO之路小组中提问。本文所涉及的源代码可以在这里下载

我的VSTO之路(五):Outlook初步开发之联系人扩展的更多相关文章

  1. IOS学习之路五(SpriteKit 开发飞机大战小游戏一)

    参考SpriteKit 创建游戏的教程今天自己动手做了一下,现在记录一下自己怎么做的,今天之做了第一步,一共有三个部分. 第一步,项目搭建. 项目所用图片资源:点击打开链接 1.在Xcode打开之后, ...

  2. 我的VSTO之路(四):深入介绍Word开发

    原文:我的VSTO之路(四):深入介绍Word开发 在上一篇文章中,我介绍了Word的对象模型和一些基本开发技巧.为了更好的介绍Word插件开发,我为本文制作了一个Word书签的增强版,具体功能是让用 ...

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

    原文:我的VSTO之路(三):Word基本知识 在前一篇文章中,我初步介绍了如何如何开发一个VSTO程序,在本文中,我将进一步深入介绍Word的插件开发.Word是一个大家在日常工作中一直接触的文档工 ...

  4. 我的VSTO之路(二):VSTO程序基本知识

    原文:我的VSTO之路(二):VSTO程序基本知识 开始之前,首先我介绍一下我的开发环境:VS2010 + Office 2010,是基于.Net framework 4.0和VSTO 4.0.以下的 ...

  5. 我的VSTO之路:序

    原文:我的VSTO之路:序 VSTO是微软提供给.Net开发人员的一个接口,通过他我们可以对Office程序做一些处理.但是这个接口并不尽善尽美,相比微软的很多其他产品,VSTO的稳定性并不好,相关的 ...

  6. ReactNative新手学习之路04 组件化开发轮播图swiper支持安卓和IOS

    react native 新手之路04 组件化开发轮播图swiper支持安卓和IOS npm install react-native-carousel --save git 地址Properties ...

  7. java web 学习五(servlet开发1)

    一.Servlet简介 Servlet是sun公司提供的一门用于开发动态web资源的技术. Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个Java程序向 ...

  8. VSTO 学习笔记(十一)开发Excel 2010 64位自定义公式

    原文:VSTO 学习笔记(十一)开发Excel 2010 64位自定义公式 Excel包含很多公式,如数学.日期.文本.逻辑等公式,非常方便,可以灵活快捷的对数据进行处理,达到我们想要的效果.Exce ...

  9. BAT解密:互联网技术发展之路(5)- 开发层技术剖析

    BAT解密:互联网技术发展之路(5)- 开发层技术剖析 1. 开发框架 在系列文章的第2篇"BAT解密:互联网技术发展之路(2)- 业务怎样驱动技术发展"中我们深入分析了互联网业务 ...

随机推荐

  1. android 32 Gallery:横着滚动的列表

    Gallery:横着滚动的列表 mainActivity.java package com.sxt.day05_01; import java.util.ArrayList; import java. ...

  2. mysql --batch --skip-column-name --execute 使用

    mysql -h 127.0.0.1 -P 3306 -u root -p -D test --batch --skip-column-name --execute="select * fr ...

  3. Excel VBA批量修改文件夹下的文件名

    今天,有同事提出想批量修改文件名,规则比较简单,在第五位后加“-”即可, 上网没找到相关工具,就自己做了个excel,用宏代码修改. 代码如下: Private Sub CommandButton1_ ...

  4. 再回首,Java温故知新(十):数组

    数组是程序中用来存储同一类型值的集合,基本所有编程语言中都有这种类型存在.声明数组时需要指出数组类型和数组变量名,初始化后数组的长度不可变,使用过程中通过下标来访问数组中的元素,示例代码如下: int ...

  5. iBatis 的删除一条记录

    Student.xml 设置删除参数的类型,可以是一个对象的 <delete id="delStudent" parameterClass="int" & ...

  6. java中的类集框架

    1.什么是类集框架 1.是一组类和接口 2.位于java.util包当中 3.主要用于用户存储和管理对象 4.主要分为三大类——集合.列表和映射 2.类集框架图 虚线框的表示接口,实线框的表示实现类 ...

  7. asp.net 批量下载实现(打包压缩下载)

    1.aspx <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default ...

  8. NSJSONSerialization-JSON数据与NSDictionary和NSArray之间的转化

    转载▼     在iOS  5 中,苹果引入了一个解析JSON串的NSJSONSerialization类. 通过该类,我们可以完成JSON数据与NSDictionary和NSArray之间的转化. ...

  9. JS特殊符号

    反斜杠用来在文本字符串中插入省略号.换行符.引号和其他特殊字符. 代码 输出 \' 单引号 \" 双引号 \& 和号 \\ 反斜杠 \n 换行符 \r 回车符 \t 制表符 \b 退 ...

  10. Sublime Text 3运行JavaScript控制台

    Node.js是一个基于Chrome JavaScript运行时建立的平台,小巧方便搭建.运行的端口可以在浏览器上运行,显示效果,但每次用浏览器也挺麻烦,我们这里讲的是在sublime text2中配 ...