最近实习在做outlook插件开发,阅读了一些VSTO的相关概念和知识。遂将整理所得与大家分享和交流。PS:这篇博客为本人的第一篇正式技术博客,如有错误和不妥之处请读者见谅。
1.VSTO外接程序体系结构
2.Outlook add-in注册表项
1.Microsoft Office 2010 应用程序可加载在 HKEY_LOCAL_MACHINE 或 HKEY_CURRENT_USER 下注册的外接程序。默认情况下,2007 Microsoft Office system 中的应用程序只能加载在 HKEY_CURRENT_USER 下注册的外接程序。
2.外接程序注册表项位于所有应用程序(Visio 除外,它的 根 为 HKEY_CURRENT_USER or HKEY_LOCAL_MACHINE)的以下注册表项之下:Root\Software\Microsoft\Office\应用程序名称\Addins\外接程序ID(外接程序ID一般为项目名)
3.Outlook 窗体区域的注册表项:Root\Software\Microsoft\Office\Outlook\FormRegions\消息类
3.安装部署
1. ClickOnce 部署:仅当前用户可以注册外接程序。这是因为 ClickOnce 只支持在 HKEY_CURRENT_USER 下创建密钥。
2. Windows Installer部署:无限制
4.定制项
UI方面
1.自定义UI
2.窗体区域/ribbon:与outlook界面相整合
逻辑方面
1.与outlook PIA或其他office PIA进行交互
2.自定义属性:用于用户自定义数据和扩展功能
5.outlook对象模型
Application 对象
Application 对象表示 Outlook 应用程序,它是 Outlook 对象模型中最高级的对象。
Explorer 对象
Explorer 对象表示显示包含项(如电子邮件、任务或约会)的文件夹内容的窗口。Explorer 对象包括可用来修改窗口的方法和属性,以及窗口更改时所引发的事件。
Inspector 对象
Inspector 对象表示显示单个项(如电子邮件、任务或约会)的窗口。Inspector 对象包括可用来修改窗口的方法和属性,以及窗口更改时所引发的事件。
MAPIFolder 对象
MAPIFolder 对象表示包含电子邮件、联系人、任务及其他项的文件夹。Outlook 提供 16 个默认 MAPIFolder 对象。
MailItem、AppointmentItem、TaskItem、ContactItem分别对应邮件项、约会项、任务项、联系人项
6.卸载
1.手动删除(控制面板\程序\卸载程序)
2.删除注册表(其实并未完整卸载,只是outlook不能检测到。控制面板\程序中仍有该插件信息)
3.禁用插件(推荐:使outlook将插件禁用,但保留在加载项栏中。想重用时可以手动启用。)
后两项都能用代码完成。
以下demo在VS 2010下完成。
1.VS 为office拓展程序开发提供了很好的开发项。创建OutlookAddIn:File->New->Project->Visual C#->Office->2010(2007)->Outlook 2010 Add-in.
2.命名好工程名确认后,系统会自动生成ThisAddIn类以及一些相应的事件。而ThisAddIn类就是整个拓展程序的一个抽象表示。
3.现在,我们可以添加自己的定制项。这里我首先添加一个窗体区域(FormRegion),窗体区域的特点是可以将该区域嵌入到outlook的工作区。右击工程名->Add->New Item->Outlook Form Region.点击确认后会进入FormRegion的导航设置 框。分别有一下几项:
一.创建方式:创建新窗体区域/从已有窗体区域导入(这里选择前者)
二.窗体样式:按窗体位置和区域分有四种样式(这里选择第二项:adjoining加在工作窗体的底部)
三.添加描述和呈现喜好(这里选择默认)
四.选择呈现该窗体区域的消息类。由于我们是要做一个简单的联系人的拓展程序(下面会介绍),所以我们呈现该窗体区域的消息类选择Contact。
设置完成点击Finish窗体区域就会成功添加。这时会出现一个自定义控件,我们可以设置里面的控件和样式。
现在介绍一下我们的拓展程序。这里我们是做一个跟联系人有关的拓展程序:为联系人添加附加属性。我们知道联系人默认的属性是有限的,为了方便用户自定义属性,Outlook为我们设置了UserProperties方便用户设置各种自定义属性。这里我们就要 用它设置我们的定制项。这里我们帮联系人添加父母及其生日的附加属性。为简单起见我们需要两个textBox和两个DataTimePicker。布局完成如下:
4.接下来,我们需要处理拓展程序的逻辑部分。主要思路是定义自定义属性、绑定属性、属性变更处理。主要代码及注释如下:
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; 18 19 // Occurs before the form region is displayed. 20 // Use this.OutlookItem to get a reference to the current Outlook item. 21 // Use this.OutlookFormRegion to get a reference to the form region. 22 private void FormRegion1_FormRegionShowing(object sender, System.EventArgs e) 23 { 24 // 获得FormRegion所对应的Contact对象 25 _Contact = ((Microsoft.Office.Tools.Outlook.FormRegionControl)sender).OutlookItem as Outlook.ContactItem; 26 //_Contact = Globals.ThisAddIn.Application.ActiveInspector().CurrentItem; 27 28 // 从联系人的自定义属性中,获得母亲姓名属性 29 _MotherNameProperty = _Contact.UserProperties.Find(PROPERTY_NAME_MOTHER_NAME, Type.Missing); 30 if (_MotherNameProperty != null) 31 { 32 // 如果存在这个属性,则取出Value为控件赋值 33 tbMotherName.Text = _MotherNameProperty.Value as String; 34 } 35 else 36 { 37 // 不存在则创建这个属性 38 _MotherNameProperty = _Contact.UserProperties.Add(PROPERTY_NAME_MOTHER_NAME, Outlook.OlUserPropertyType.olText, Type.Missing, Type.Missing); 39 } 40 41 // 母亲生日,原理相同 42 _MotherBirthdayProperty = _Contact.UserProperties.Find(PROPERTY_NAME_MOTHER_BIRTHDAY, Type.Missing); 43 if (_MotherBirthdayProperty != null) 44 { 45 dtpMotherBirthday.Value = (DateTime)_MotherBirthdayProperty.Value; 46 } 47 else 48 { 49 _MotherBirthdayProperty = _Contact.UserProperties.Add(PROPERTY_NAME_MOTHER_BIRTHDAY, Outlook.OlUserPropertyType.olDateTime, Type.Missing, Type.Missing); 50 } 51 52 // 父亲姓名 53 _FatherNameProperty = _Contact.UserProperties.Find(PROPERTY_NAME_FATHER_NAME, Type.Missing); 54 if (_FatherNameProperty != null) 55 { 56 tbFatherName.Text = _FatherNameProperty.Value as String; 57 } 58 else 59 { 60 _FatherNameProperty = _Contact.UserProperties.Add(PROPERTY_NAME_FATHER_NAME, Outlook.OlUserPropertyType.olText, Type.Missing, Type.Missing); 61 } 62 63 // 父亲生日 64 _FatherBirthdayProperty = _Contact.UserProperties.Find(PROPERTY_NAME_FATHER_BIRTHDAY, Type.Missing); 65 if (_FatherBirthdayProperty != null) 66 { 67 dtpFatherBirthday.Value = (DateTime)_FatherBirthdayProperty.Value; 68 } 69 else 70 { 71 _FatherBirthdayProperty = _Contact.UserProperties.Add(PROPERTY_NAME_FATHER_BIRTHDAY, Outlook.OlUserPropertyType.olDateTime, Type.Missing, Type.Missing); 72 } 73 74 // 将这四个控件绑定change事件,只有在修改之后,我们才会将值回写到Contact对应的属性中去 75 tbMotherName.TextChanged += new EventHandler(content_Changed); 76 dtpMotherBirthday.ValueChanged += new EventHandler(content_Changed); 77 tbFatherName.TextChanged += new EventHandler(content_Changed); 78 dtpFatherBirthday.ValueChanged += new EventHandler(content_Changed); 79 80 // 在Write事件中,把修改的值保存到属性中去 81 _Contact.Write += new Microsoft.Office.Interop.Outlook.ItemEvents_10_WriteEventHandler(contact_Write); 82 } 83 84 void content_Changed(object sender, EventArgs e) 85 { 86 // 有修改时,将_Change置为true 87 _Changed = true; 88 } 89 90 void contact_Write(ref bool Cancel) 91 { 92 if (_Changed) 93 { 94 // 保存值到属性中去 95 _MotherNameProperty.Value = tbMotherName.Text.Trim(); 96 _MotherBirthdayProperty.Value = dtpMotherBirthday.Value; 97 _FatherNameProperty.Value = tbFatherName.Text.Trim(); 98 _FatherBirthdayProperty.Value = dtpFatherBirthday.Value; 99 } 100 101 } 102 103 // Occurs when the form region is closed. 104 // Use this.OutlookItem to get a reference to the current Outlook item. 105 // Use this.OutlookFormRegion to get a reference to the form region. 106 private void FormRegion1_FormRegionClosed(object sender, System.EventArgs e) 107 { 108 // 关闭事件绑定 109 _Contact.Write -= new Microsoft.Office.Interop.Outlook.ItemEvents_10_WriteEventHandler(contact_Write); 110 111 // 释放对象 112 System.Runtime.InteropServices.Marshal.ReleaseComObject(_Contact); 113 _Contact = null; 114 }
现在,拓展程序已经可以调试了。运行后outlook程序会自动启动并加载此拓展项。打开联系人信息工作区会看到刚才的窗体区域被成功地加了进去。
好了。我们联系人的自定义属性已经做好了。为了实例的完整性,现在我们再加一个ribbon控件,使其附加在本地outlook的ribbon菜单项内。其功能为:一.显示联系人信息(主要是我们刚才添加的附加属性信息);二.拓展程序卸载/禁用。
5添加ribbon:右击工程名->Add->New Item->Ribbon (Visual Designer).点击确认后会看到一个ribbon控件被添加了进来。值得注意的是RibbonType属性,它决定着ribbon的显示位置默认为Mail.Read。由于我们要将该ribbon显示在主窗体的 ribbon项集中,因此其RibbonType应勾选Explore(同理如何想让该ribbon显示在联系人窗体的ribbon项中可勾选Contact)。现在我们要为其添加两个RibbonButton:分别为Show Contacts Info和Unistall。
6.针对Show Contacts Info为了显示联系人信息,我们需要添加一个窗体来进行显示。于是我们可以添加一个UserControl名为ContactsInfoDisplay,其形式如下:
为了显示联系人信息,我们可以传入一个ContactItem作为其构造函数的参数。然后获取其对象中的相关属性。代码如下:
1 public ContactsInfoDisplay(Microsoft.Office.Interop.Outlook.ContactItem contact) 2 { 3 InitializeComponent(); 4 this.name.Text = contact.LastName+" "+contact.FirstName; 5 this.phone.Text = contact.MobileTelephoneNumber; 6 this.email.Text = contact.Email1Address; 7 //获取自定义属性 8 this.father.Text = string.Format("{0}({1})", contact.UserProperties.Find("PROPERTY NAME FATHER NAME").Value, contact.UserProperties.Find("PROPERTY NAME FATHER BIRTHDAY").Value); 9 this.mother.Text = string.Format("{0}({1})", contact.UserProperties.Find("PROPERTY NAME MOTHER NAME").Value, contact.UserProperties.Find("PROPERTY NAME MOTHER BIRTHDAY").Value); 10 }
7.针对Unistall,我们有两种策略(第一部分提到)。
一:删除注册表:第一部分提到过outlook加载插件时先检查注册表里的特定目录下的项。如果删除该项,outlook就不会检测到从而也就不会加载。代码如下:
1 RegistryKey regKey = null; 2 RegistryKey regSubKey = null; 3 try 4 { 5 //Read the key 6 regKey = Microsoft.Win32.Registry.CurrentUser; 7 //获取outlook插件目录下的子键 8 regSubKey = regKey.OpenSubKey(string.Format(@"Software\Microsoft\Office\Outlook\Addins"), true); 9 //获取当前插件名 10 regSubKey.DeleteSubKey(Assembly.GetExecutingAssembly().GetName().Name); 11 System.Windows.Forms.MessageBox.Show("插件卸载成功,下次启动生效!"); 12 } 13 catch (Exception e) 14 { 15 System.Windows.Forms.MessageBox.Show(e.Message); 16 }
二:禁用插件:由于第一种方法只是处理注册表,因此卸载的不是很彻底。还有很多遗留项。因此推荐使用第二种方法。使outlook将插件禁用,但保留在加载项栏中。想重用时可以手动启用。其相应的代码也很简单。
1 Microsoft.Office.Core.COMAddIn addin= Globals.ThisAddIn.Application.COMAddIns.Item("OutlookAddInDemo"); 2 addin.Connect = false;
到现在我们的实例已经完整的介绍完了,运行结果如图。完整工程源代码下载:http://files.cnblogs.com/maxliu/OutlookAddInDemo.rar。