Outlook 自动化
本文展示了如何使用 VS2008 自动化 Outlook,而不是 VSTO 或 VBA。

引言
本文旨在帮助那些希望通过提供一个易于实现的类来增强Microsoft Outlook的VS2008程序员,该类能极大地简化在Outlook菜单栏中实现按钮和弹出菜单的繁琐(且易出错)过程。
背景
Microsoft Outlook 是一个丰富的开发平台,为改进这款主流信息管理程序的内置功能提供了充足的机会。有三种方法可以实现这一目标:
- 使用 VSTO 开发外接程序
- 编写 VBA 代码
- 使用 Visual Studio 2008 开发自动化可执行文件
E.Carter & E.Lippert [1] 对这三种策略的相对优势进行了很好的讨论,尽管重点在于 VSTO 的外接程序开发。外接程序的开发似乎异常困难:调试并非易事,并且在目标机器上的正确注册往往不稳定。使用 VBA 的限制性相当大,因为它目前无法完全访问 .NET 平台。使用 VS2008 开发自动化可执行文件似乎是最好的选择:这是我们熟悉的开发环境,并且可以访问 VS2008 集成开发环境提供的全套调试技术。此外,如果最终产品必须是外接程序,则在完全调试后,生成的代码可以相对轻松地移植。
Using the Code
我在此提供的代码旨在为您提供开发自己的自动化产品所需的框架。您需要在 VS2008 项目中添加以下引用:
- Microsoft Office 11.0 (或 12.0) 对象库
- Microsoft Outlook 11.0 (或 12.0) 对象库
代码组织成一个单独的类(OutlookMenuExtensions_Class_src.cs),该类完成了以下基本步骤:
- 它检查用户是否为单个自定义菜单按钮和/或自定义弹出菜单项以及关联的下拉菜单项提供了非空的标题。
- 它将连接到一个已运行的 Outlook 实例,或者创建一个新的 Outlook 实例(一次只能运行一个)。
- 它确保请求的菜单项已添加到 Outlook 应用程序的“主菜单”命令栏(适用于资源管理器和检查器实例)。关联的
CommandBarButton
对象存储在Office.CommandBarButton
类型的列表中。
实例化 OutlookMenuExtensions
类的代码需要定义 Outlook 资源管理器和检查器的单个菜单按钮和/或弹出菜单及其关联菜单项的标题。事件连接最好是通过将所有相似的按钮连接到单个单击事件处理程序来完成,然后该处理程序需要通过使用它们的 Tag
属性来区分触发事件的按钮(下面有更多详细信息)。
我包含了一个实现示例(ImplementationExample_src.cs)。这是代码的主要部分(我稍后将解释引用 API 方法 SetForegroundWindow
的原因、WaitForEvents()
方法背后的细节以及注释行中引用的 LicenseGenie
类)。
public YourApplication()
{
//LicenseGenie myLicenseGenie = new LicenseGenie();
//if (!myLicenseGenie.LicenseIsValid()) return;
//Explorer Menu Items
oL.NewExplorerMenuItem_Caption = "My Explorer Button";
oL.NewExplorerMenuItem_Tag = "My Explorer Button";
oL.NewExplorerMenuPopUp_Caption = "My Explorer Popup";
oL.NewExplorerPopupMenuItem_Captions = new string[3]
{ "Explorer Item 1", "Explorer Item 2", "Explorer Item 3" };
oL.NewExplorerPopupMenuItem_Tags = new string[3]
{ "Explorer Item 1", "Explorer Item 2", "Explorer Item 3" };
//Inspector Menu Items
oL.NewInspectorMenuItem_Caption = "My Inspector Button";
oL.NewInspectorMenuItem_Tag = "My Inspector Button";
oL.NewInspectorMenuPopUp_Caption = "My Inspector Popup";
oL.NewInspectorPopupMenuItem_Captions = new string[3]
{ "Inspector Item 1", "Inspector Item 2", "Inspector Item 3" };
oL.NewInspectorPopupMenuItem_Tags = new string[3]
{ "Inspector Item 1", "Inspector Item 2", "Inspector Item 3" };
oL.SetupMenus();
if (!oL.LaunchSuccessful) return;
//Now wire up the event handlers
oL.c_NewExplorerMenuItem[0].Click +=
new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler
(Explorer_Menu_Click);
for (int i=0; i < oL.NewExplorerPopupMenuItem_Captions.Length;i++)
oL.c_NewExplorerPopUpMenuItem[i].Click +=
new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler
(Explorer_Menu_Click);
oL.c_NewInspectorMenuItem[0].Click +=
new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler
(Inspector_Menu_Click);
for (int i = 0; i < oL.NewInspectorPopupMenuItem_Captions.Length; i++)
oL.c_NewInspectorPopUpMenuItem[i].Click +=
new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler
(Inspector_Menu_Click);
c_Outlook = oL.c_App;
WaitForEvents();
}
您的自定义代码可以轻松地在单击处理程序中实现。我建议为资源管理器和检查器菜单控件使用单独的处理程序。您需要自行决定与特定菜单按钮关联的自定义方法是否适合当前上下文。我建议您分别使用 c_Outlook.ActiveExplorer().Caption
或 c_Outlook.ActiveInspector().Caption
来进行判断。
在我的实现示例(Program.cs)中,我使用了通用的单击处理程序,这些处理程序会简单地显示一个表单,指示用户按下了哪个按钮。由于我无法确定的原因,通过调用以下方法无法正确完成此操作:
MessageBox.Show("User clicked " + Ctrl.Tag)
第一次在应用程序启动后按下按钮时,MessageBox
无法可靠地冒泡到窗口堆栈的顶部(在后续单击时工作得很好。我通过将自定义表单(名为“alert
”)的 Activated
事件连接到以下事件处理程序来解决此问题,该处理程序通过使用 API 函数 SetForegroundWindow
确保警报表单始终冒泡到顶部。
void alert_Activated(object sender, EventArgs e)
{
SetForegroundWindow(((Form)sender).Handle.ToInt32());
}
WaitForEvents()
方法的代码很简单。它旨在确保您的应用程序能够监听 Outlook 应用程序发出的事件,并且在用户关闭 Outlook 应用程序时您的应用程序能够关闭(我假设当 Outlook 应用程序不再显示任何资源管理器或检查器窗口时就是这种情况;实际上 Outlook 可能会持续存在,例如,如果用户没有响应所有提醒)。
private void WaitForEvents()
{
do
{
Application.DoEvents();
if (c_Outlook.Explorers.Count == 0 & c_Outlook.Inspectors.Count == 0)
{
MessageBox.Show("User just terminated the last Outlook session\
nand hence this Outlook Sidekick is going to close as well.");
((Microsoft.Office.Interop.Outlook._Application)c_Outlook).Quit();
break;
}
Thread.Sleep(500);
} while (true);
}
最后,如承诺的,关于我称之为 LicenseGenie
的类的注释行的一些话。实现此类的样子将会在您的应用程序中包含一个完整的用户许可管理框架,而无需编写任何其他代码!如果您觉得这很有趣,我邀请您在 SoarentComputing 的网站上了解有关 LicenseGenie 的更多信息。
在 SoarentComputing 的网站上可以找到一个实现 OutlookMenuExtensions
类的应用程序。它名为 OutlookSidekick2009,并提供了两个对 Outlook 有用的附加功能:
- 它消除了在创建日历条目时通常需要的多步骤复制粘贴过程,该过程从包含会议邀请的电子邮件消息创建,而这些会议邀请来自不使用 Outlook 内置会议请求功能的客户端,并且
- 它会拦截用户忘记在电子邮件中包含附件的情况。
关注点
既然我已经提供了 OutlookMenuExtensions
类的代码供有兴趣深入研究的任何人学习,我将在此只提供一些说明。
在 Microsoft Office 套件的所有应用程序中,Outlook 在其命令栏架构方面可以说是最复杂的(而 Excel 也可以说是最简单的)。Outlook 的每个资源管理器(例如,收件箱、日历、联系人等)都有自己的命令栏。Outlook 的检查器(即 Outlook 中存储的任何项目的详细视图,例如,邮件项、日历项、联系人项等)也是如此。用户可以打开任意数量的 Outlook 资源管理器和检查器。
在开始这个项目时,我曾认为可能有一个主命令栏适用于资源管理器和检查器,我可以在其中添加自定义按钮和弹出菜单,但事实证明,每个资源管理器和检查器默认使用原始的资源管理器和检查器菜单栏,然后需要为每个新实例进行扩展。对于试图用保持一致可访问的菜单按钮来扩展相关命令栏的开发人员来说,这种情况很容易变成一场噩梦。CommandBarButton
控件的数量需要随着打开的资源管理器和检查器数量进行扩展。然而,通过使用 Office.CommandBarButton
类型的列表来处理自定义菜单控件,可以轻松地实现这一点。为了简化问题,我决定不 bother 删除用户关闭了其菜单栏中的按钮的资源管理器或检查器时不再使用的任何 CommandBarButton
。这显然是一个潜在的内存占用者,但我认为在正常的 Outlook 使用情况下,这不应该成为一个问题。
尽管有大量的 CommandBarButton
,但只需要显式连接第一个单个自定义菜单按钮和第一个自定义弹出菜单项集,因为重复项的 Tag
属性是相同的。实际上,尝试连接超过第一组按钮会导致多次触发。
我已经提到了我遇到的一个意外情况:应用程序显示的 MessageBox
在首次激活时不会冒泡到窗口堆栈的顶部,但我已经描述了如何解决这个麻烦。然而,我未能解决另一个窗口的麻烦。每当从一个打开的 Outlook 资源管理器启动一个新的检查器时,随后单击自定义 CommandBarButtons
中的一个会产生一个不期望的效果:一旦点击事件处理程序完成其任务,Outlook 会返回到启动新检查器的资源管理器,但仅在第一次!如果能阻止这种情况就好了,但我最终放弃了,现在希望读到这篇文章的人能够提出一个解决方案。
我需要就此代码的线程单元模型再提一点。事实证明,尽管我努力确保实例化 OutlookMenuExtensions
的进程使用单线程单元(STA)模型,方法包括在 Main
方法前面插入 [STAThreadAttribute]
,但代表 Outlook 进程的线程使用的是多线程单元(MTA)模型。因此,处理 Outlook 事件的方法不能包含任何需要 STA 模型的方法(例如,Clipboard
类的任何方法)。
参考文献
[1] "Visual Studio Tools for Office 2007: VSTO for Excel, Word and Outlook", E.Carter & E.Lippert, Addison-Wesley, 2009
[2] "Programming Applications for Microsoft Office Outlook 2007", R. Byrne & R. Gregg, Microsoft Press, 2007
历史
- 首次修订于 2009 年 6 月