65.9K
CodeProject 正在变化。 阅读更多。
Home

Outlook 2010 加载项 自定义电子邮件分类和属性

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (7投票s)

2012 年 3 月 20 日

Apache

11分钟阅读

viewsIcon

53807

Outlook 2010 电子邮件分类和归因。

引言

本文提供了在 Microsoft Outlook 2010 中创建分类和归因系统所需的各种机制的蓝图。它还提供了一些常见场景的示例。

背景

电子邮件长期以来一直没有变化。当然,有一些小的标签、类别、规则、对话、文件夹、附件等等,但没有什么真正惊天动地的。电子邮件的自我描述是缺失的。

我在一家大型制造公司的 IT 部门工作,我们对项目和程序的执行定义了非常严格的流程。有文章/文档定义了在软件开发生命周期和/或业务流程中要捕获什么。有系统可以捕获这些信息。有报告可以汇总和显示所有这些结果。然而,我仍然看到,最有价值的信息仍然会出现在电子邮件中。内容最终可能会进入某个固有的系统,但当我寻找某些东西时,我首先会查阅电子邮件存档。据我所知,其他人也是如此。

因此,Microsoft Outlook 应运而生。它是迄今为止功能最丰富的电子邮件客户端之一。然而,电子邮件仍然非常扁平,你可能会将消息放入特定的文件夹,为其应用类别/标签……但仅此而已。问题是,如果我有一封电子邮件实际上属于多个文件夹怎么办?多个标签?如果我想将这封电子邮件用作项目的参考数据怎么办?

如何?这些事情能完成吗?很简单。

分类和归因。创建分类、关系和安全模型,使电子邮件最终能够实现这一目标。

所以,以 Outlook 为起点进行尝试,也许还没有完全实现我取代社交媒体、将我的标签引擎推向云端的愿景……但让我们从小处着手……如果我想在一个现有工具(比如 Outlook)上进行归因和分类,因为我每天都在使用它,我开始进行概念验证,看看这如何实现。

我发现 Outlook 的设计 DNA 专门针对我想要做的事情。我希望 Microsoft 阅读这篇文章,因为他们已经准备好了一切,只需要一些微调就可以让它全部工作。

使用代码

我的例子从 Microsoft Ribbon 示例开始。这使您可以轻松地为电子邮件项目添加右键单击事件。请从 Microsoft 下载并使用此 Ribbon 示例:http://msdn.microsoft.com/en-us/library/ee692172.aspx

如下修改 explorer.xml 文件以启用右键单击事件。您可以添加任意数量的按钮。

<contextMenu idMso="ContextMenuMailItem">
        <button id="MyContextMenuMailItem4"
           label="TagIT" 
           onAction="TagEmailItem"/>
   </contextMenu> 
</contextMenus>

至于管理屏幕按钮(提前说明,稍后会详细介绍)。你可以这样做

<ribbon>
    <tabs>
      <tab id="MyTab"
           getVisible="MyTab_GetVisible"
           label="TagIT">
        <group label="Core" id="MyGroup1" >

       <button id="MyButton2"
        size="large"
        label="TagIT!"
        imageMso="Piggy"
        screentip="Add Tags to your message"
        supertip="Written by Scott Traube"
        onAction="LaunchTagging"/>

       <button id="MyButton"
        size="large"
        label="Find your TagIT Emails"
        imageMso="FindText"
        screentip="Find Emails based on custom tags"
        onAction="LaunchSearch"/>
        </group>
        <group label="Admin" id="MyGroup2" >
          <button id="MyButton3"
        size="large"
        label="Tag Administration"
        imageMso="MagicEightBall"
        screentip="Modify Class Tags and Attributes"
        onAction="LaunchAdmin"/>
        </group>
      </tab>
    </tabs>
</ribbon> 

步骤 1。我如何标记(顺便说一下,我的概念验证应用程序叫做 TAG-IT)电子邮件?进入 MSDN 和 Google(当然还有 Bing)。我最初的想法是,好吧,只需在每封电子邮件的底部附加 XML,因为我当时对 Outlook 插件一无所知。所以,XML 附加到末尾,你可以做到,它足够简单,你获取目标消息的句柄,附加到正文并保存它……完成。但当你这样做时,如果你的电子邮件是 DOC 或 HTML 类型,天哪,那会完全搞乱格式!虽然可以做到。但这可能不是正确的方法。当我更深入地探索时,我发现存储在 Outlook 中的每个电子邮件项目都有用户定义的属性。啊哈!经过一些快速测试后我发现,它非常适合我想要完成的事情,它轻量级,占用空间很小,可搜索,开箱即用。好的,那么我如何设置用户定义的属性?你可以通过在上面的 XML 中定义一个函数来开始,就像

<button id="MyContextMenuMailItem4" label="TagHello" onAction="helloworld"/>

现在右键单击一封电子邮件会触发这个

public void helloworld(Office.IRibbonControl control)
{
    if (control.Context is Outlook.Selection)
    {
        Outlook.Selection selection =
            control.Context as Outlook.Selection;
        if (selection.Count == 1)
        {
            if (selection[1] is Outlook.MailItem)
            {
                Outlook.MailItem oMailItem =  selection[1] as Outlook.MailItem;
                oMailItem.UserProperties.Add("TagITUpdated",
                Outlook.OlUserPropertyType.olDateTime, false,
                Outlook.OlUserPropertyType.olDateTime);
               
                oMailItem.UserProperties["TagITUpdated"].Value = DateTime.Now;
                    
                oMailItem.UserProperties.Add("MyCustomValue",
                Outlook.OlUserPropertyType.olText, true, 
                Outlook.OlUserPropertyType.olText); 
                oMailItem.UserProperties["MyCustomValue"].Value =  "Hello World";
                oMailItem.Save();
            }
        }
    }
}

就是这样。所以我基本上只是为这封特定的电子邮件添加了名为 MyCustomValue 的隐藏属性,并将值设置为 Hello World。我还对 TagITUpdated 属性进行了日期戳。

如果我想查看我在电子邮件上设置的值,我可以创建另一个按钮,其函数调用如下

public void ShowUserProperties(Outlook.MailItem mail) 
{ 
    Outlook.UserProperties mailUserProperties = null; 
    Outlook.UserProperty mailUserProperty = null; 
    StringBuilder builder = new StringBuilder(); 
    mailUserProperties = mail.UserProperties; 
    try 
    { 
        for (int i = 1; i <= mailUserProperties.Count; i++) 
        { 
            mailUserProperty = mailUserProperties[i]; 
            if (mailUserProperty != null) 
            {
                builder.AppendFormat("Tag: {0} \tValue: {1} \n\r"  mailUserProperty.Name, 
                                     mailUserProperty.Value);
                Marshal.ReleaseComObject(mailUserProperty); 
                mailUserProperty = null; 
            } 
        }
        if (builder.Length > 0)
        {
          System.Windows.Forms.MessageBox.Show(builder.ToString(),
                 "Hidden Property Values");
        }
        else
        {
            System.Windows.Forms.MessageBox.Show("No Hidden Property values found!", 
                                                 "Notification");
        }
    } 
    catch (Exception ex) 
    { 
        System.Windows.Forms.MessageBox.Show(ex.Message); 
    } 
    finally 
    { 
        if (mailUserProperties != null) 
            Marshal.ReleaseComObject(mailUserProperties); 
    } 
}

步骤 2。那么,如果我想为标记电子邮件设置自定义分类和归因,我需要将参考数据存储在某个地方,对吗?嗯,也许是本地 Access 数据库、MySQL、XML、平面文件?都可行,但是开销很大……如果我使用多台计算机访问同一个 Exchange 服务器怎么办?如果我有一个本地 XML 文件,那么没有一些花哨的操作,其他客户端就无法访问它。所以最后,我挖了一下,找到了存储项!Bingo!再一次,Microsoft 显然是专门为我设计的!我可以使用一个隐藏的存储项来存储每个类、属性类型、属性名称、预定义属性标准……所有这些都存储在这些位于 Exchange 上的隐藏存储项中!要正确地做到这一点,我会获取一个 XML 对象,将其序列化为字符串,然后将其放入 StorageItem 的 Body 中。太棒了!所以这就是你如何做到这一点

private void AddtoStorage(string storageIdentifier, string storageContent)
{
    if (!String.IsNullOrEmpty(storageIdentifier) && 
              (!String.IsNullOrEmpty(storageContent))
    {
        Outlook.MAPIFolder folder = 
           Globals.ThisAddIn.Application.GetNamespace(
           "MAPI").GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
        Outlook.StorageItem storageitem = folder.GetStorage(
           storageIdentifier, Outlook.OlStorageIdentifierType.olIdentifyBySubject);
        storageitem.Body = storageContent;
        storageitem.Save();
    }
}
 
private string GetfromStorage(string storageIdentifier)
{
    if (!String.IsNullOrEmpty(storageIdentifier))
    {
        Outlook.MAPIFolder folder = Globals.ThisAddIn.Application.GetNamespace(
          "MAPI").GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
        Outlook.StorageItem storageitem = folder.GetStorage(
          storageIdentifier, Outlook.OlStorageIdentifierType.olIdentifyBySubject);
        try
        {
            string bodycontent = storageitem.Body.ToString(); 
            return bodycontent;
        }
        catch (Exception e)
        {
            return "";
        }
    }
    else
    {
        return "";
    }
}

步骤 3。现在我有了存储分类定义的方法,也有了在单个电子邮件上存储属性的方法。但是如何将它们联系起来呢?很简单,在你的插件项目中创建几个 Windows Forms!我有 3 个。

  1. 一个管理表单,用于创建、修改你的分类。它从顶部功能区启动。  
  2. 一个你可以根据使用你的管理表单创建的类和属性来标记你的单个电子邮件(用户属性)的表单。它在你右键单击电子邮件时启动。
  3. 一个搜索表单,可以让你通过使用你的管理表单创建的类和属性进行一次点击归因。它从顶部功能区启动。  

管理示例: 

执行实际的标记

 

执行搜索

 

 

实现这样的功能会变得有点复杂。所以我会留下一个从右键单击事件启动 Windows 窗体的示例。我所做的是获取 StoreID(PST、Exchange,无论此电子邮件当前驻留在何处)和 EntryID,后者是电子邮件在此特定存储位置中的唯一标识符。两者结合起来是该电子邮件的主键。

public void LaunchTagging(Office.IRibbonControl control)
{
    if (control.Context is Outlook.Selection)
    {
        Outlook.Selection selection = control.Context as Outlook.Selection;
        if (selection.Count == 1)
        {
            OutlookItem olItem = new OutlookItem(selection[1]);
             Form1 formMain = new Form1();
            formMain.StorageID = olItem.Parent.StoreID;
            formMain.EntryID = olItem.EntryID;
            formMain.ShowDialog();
        }
    }
}

上面只是获取 ID 并在您要打开的此表单中设置它们,然后它启动该表单。

注意,我在表单中有这个(好的,你可能不需要这个例子,但它在这里)

private string entryID = "N/A";
private string storageID = "N/A";  
public string EntryID
{
    get
    {
        return entryID;
    }
    set
    {
        entryID = value;
    }
}

public string StorageID
{
    get
    {
        return storageID;
    }
    set
    {
        storageID = value;
    }
}

因此,当表单启动后,我可以打开该特定电子邮件并应用分类。

这就是我根据 storageIDentryID 打开它的方式

Outlook._Application olApp = new Outlook.ApplicationClass();
Outlook._NameSpace olNS = olApp.GetNamespace("MAPI");
Outlook.MAPIFolder oFolder = olNS.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
Outlook._MailItem oMailItem = (Outlook._MailItem)olNS.GetItemFromID(entryID, storageID);

所以 oMailItem 是我将要添加归因的对象。

这可能是一种您使用屏幕上选定的内容来设置用户属性的方式

 // look at the first field
int llindex = listLeft.SelectedIndex;
if (!String.IsNullOrEmpty(lblLeft.Text.ToString() ) && llindex != -1)
{
//save left                   
oMailItem.UserProperties.Add(lblLeft.Text,
Outlook.OlUserPropertyType.olText, true, Outlook.OlUserPropertyType.olText);           
oMailItem.UserProperties[lblLeft.Text].Value = listLeft.SelectedItem.ToString();
}
// look at the cneter field
int lcindex = listCenter.SelectedIndex;
if (!String.IsNullOrEmpty(lblCenter.Text.ToString()) && lcindex != -1)
{
//save center                   
oMailItem.UserProperties.Add(lblCenter.Text, 
  Outlook.OlUserPropertyType.olText, true, Outlook.OlUserPropertyType.olText);                   
oMailItem.UserProperties[lblCenter.Text].Value = listCenter.SelectedItem.ToString();
}
oMailItem.Save();   

示例。我有一个名为 XTR 的项目,它有构想、规划、开发和维护阶段。我还有不同类型的需求,如业务、系统、测试、支持。我可能同时还有另外两个项目,名为 XT 和 STX,但它们具有相同的需求类型和相同的阶段。因此,在我的管理屏幕上,我希望创建一个新的项目类。它将有三个属性:ProjectNameProjectPhaseRequirementType。此外,RequirementType 将有 4 个列表值。我将所有这些信息理想地保存为序列化的 XML 对象到存储项中。

现在,我收到一封电子邮件,是关于 XTR 项目的,我们正处于构想阶段,这封邮件来自我的开发团队。经过进一步调查,这封邮件似乎包含一些非常有用的信息,我稍后在编写系统需求规范时会用到。所以我右键点击这封邮件,它弹出了 TagIT 窗口。表单从存储项中提取可能的类别,我一键选择项目类别,表单现在向我显示该类别的底层属性,我选择“构想”阶段的 ProjectPhase 和“系统”类型的 RequirementType,然后点击 TAGIT/SAVE。快速点击 4 次,这封邮件现在变得更有用。

从代码层面来看,如果硬编码,它看起来像这样

oMailItem.UserProperties.Add("ProjectName", 
  Outlook.OlUserPropertyType.olText, true, Outlook.OlUserPropertyType.olText); 
oMailItem.UserProperties["ProjectName"].Value = "XTR";                   
oMailItem.UserProperties.Add("ProjectPhase", 
  Outlook.OlUserPropertyType.olText, true, Outlook.OlUserPropertyType.olText);
oMailItem.UserProperties["ProjectPhase"].Value = "Envisioning"; 
oMailItem.UserProperties.Add("RequirementType", 
  Outlook.OlUserPropertyType.olText, true, Outlook.OlUserPropertyType.olText);
oMailItem.UserProperties["RequirementType"].Value = "System";
oMailItem.Save();    

请注意,Outlook 中有一些保留字段,因此如果您尝试添加一个已经存在的 UserProperty,例如 Priority、Body、Subject 等,您将会收到错误。因此,我会在管理表单中进行验证,以确保它在保存之前不存在。回到正题,稍后,如果我想查看 XTR 项目的所有系统级需求电子邮件,我拥有所有所需的信息,我只需要一个搜索屏幕来根据 XTR、构想和系统进行查询。现在,我如何找到它?Microsoft 也解决了这个问题……它是开箱即用的。

我可以打开高级搜索屏幕。转到“字段”,选择“高级”选项卡。单击“字段”,然后选择“用户定义字段”。如果您在正确的文件夹中,并且处于正确的搜索上下文中,您应该能找到名为 ProjectPhaseRequirementType 的字段。如果您搜索“构想”,结果就是您刚刚标记的那封电子邮件。

高级查找界面如下

 

关于该属性的可搜索性。请注意,对于 oMailItem.UserProperties.Add,第三项是一个 bool 类型。这用于 addtofolderfields。这意味着,对于您当前所在的文件夹,它将添加该属性,但不会添加任何值,只添加属性名称。这使得该属性在该目录中可搜索。如果您之前已经将该搜索字段添加到该文件夹,它不会重复,因此您可以根据需要多次调用它。

每次都进行高级搜索是不现实的。而且您也不想每次都手动选择它们,特别是当您在隐藏存储项中预定义了一组列表时。那么如何让搜索显示在主 Outlook 资源管理器中呢?首先,如果您转到搜索屏幕并按如下方式进行搜索:[RequirementType]:=('System') [ProjectPhase]:=('Envisioning')

主搜索窗口将提供该调用的结果。但是,如果我们希望我们的应用程序构建该搜索字符串并为我们执行调用呢?并且仍然显示在主 Outlook 资源管理器屏幕上?我们可以,就像这样从我们的自定义搜索窗口表单中

Outlook.Explorer explorer = Globals.ThisAddIn.Application.ActiveExplorer();
string searchFor = "[RequirementType]:=('System') [ProjectPhase]:=('Envisioning')";
explorer.CurrentFolder = inbox;
explorer.Display();
explorer.Search(searchFor.Trim(), Outlook.OlSearchScope.olSearchScopeSubfolders); 

所以您的搜索屏幕必须根据隐藏存储项中预填充的条件以编程方式设置 searchFor 字符串。再次强调,我们从不更新该隐藏存储项,我们只从管理屏幕设置它……然后信息是静态的,只在您进行标记或执行搜索时按需引用。

 

结果

 

现在,回到 addtofolderfields。请注意,它只在您设置用户属性时,电子邮件所在的文件夹级别添加了它。因此,如果您将该电子邮件移动到另一个文件夹或 PST,属性仍然存在,但除非该属性已经存在于该文件夹中,否则可搜索性就会消失。因此,除非您将该属性添加到要搜索的文件夹中,否则它将无法找到。这很麻烦,但解决方案很简单,您遍历所有可用文件夹并以编程方式设置属性,以便所有属性都可搜索。您可能会问如何做到这一点?方法如下

存储对象可以代表您的默认 Outlook 收件箱或 PST。

Outlook.MAPIFolder folder = store.GetRootFolder() as Outlook.Folder;
SetFolderAttributes(folder);   

SetFolderAttributes 函数基本上遍历所有子文件夹并执行此操作

try
{               
subfolder.UserDefinedProperties.Add(AttributeName,
Outlook.OlUserPropertyType.olText, Type.Missing, Type.Missing);
}   

这就是你遍历文件夹的方式

if (folder.Folders.Count > 0)
{
foreach (Outlook.Folder subfolder in folder.Folders)
{
// set the class attributes for folder for each attribute for all classes
    for (int i = 0; i < TagAttributes.Count; i++)
        {
        th.SetSubfolderAttributes(subfolder, TagAttributes[i].ToString());
        }  
}
}   

一旦你完成了这些,你的所有文件夹都是可搜索的。当你搜索隐藏属性时,它会找到它。

More Example Classes the user could create.
HumanResources (class)
    HRTAG (Attribute name)
        "Year End Feedback"
        "Mid Year Feedback"
        "PBA Vacation Related"
    HRPerson (Attribute name)
        "EmployeeX"
        "EmployeeY"
        "EmployeeZ"
Assuming I'm a manager, when I get feedback form business partners that is negative 
or positive and reflective of an individual contributor's performance, I can Tag it with 
that employee's name and year end feedback. At the end of the year, 
I can search on those two attributes and have the information I need.
Or perhaps something like this:
CustomerResponse (Class)
    CustomerType (Attribute Name)
        "Relationship"
        "Federal"
        "Education"
        "Small Business"
        "Enterprise" 
    ResponseType
        "shipping question"
        "Product Praise"
        "Product Defect"
        "Order Related"
    ResponsePriority
        "High"
        "Medium"
        "Low"  

希望您喜欢这个,并能激发一些想法。

关注点

关于这个可以和应该去往何处的其他想法。

  • 在公司或部门层面设置分类。这样它们就标准化了。您需要做的是确定一个位置,也许是 SharePoint 列表,或者是云端……然后从客户端的角度,您将从主位置读取并本地持久化到存储项中。
  • 能够将电子邮件分类为“项目可见”或“部门可见”,这意味着,一旦用户以这种方式对其进行分类,它就不再局限于本地用户或收件人/抄送/密送中的用户,它将聚合到更高的级别,进入云端,仍然在分类的范围内获得授权,但电子邮件现在变得更大了,也许我是部门级别列表的关注者,所以一旦有人以这种方式对其进行分类,我就会在那里看到它,所有其他有权访问该部门的人也会看到它……也就是类似 Twitter、Google+ 的思维方式,对吗?但在电子邮件上,大脑会更容易处理……你明白我的意思吗?
  • 云端的 TagIT 引擎。标记新闻报道、标记电子邮件、标记推文(他们已经有了标签)……基本上,标记任何东西和所有东西。现在开始分析标签和使用它们的用户趋势……也许我想阅读与我标记方式相似的新闻报道……相似的兴趣。换句话说,最简单的可视化方式是 Netflix 评级系统……结果呢?我的新闻不再是政治、政治、政治……它是量身定制的(骑行、铁人三项、山地自行车、科技新闻)。这与目前事物的运作方式相反,广告引擎会汇总你的去向、你看到的内容,为什么不直接问用户呢?!(这也与我的用户管理反向 cookie 概念有关,但那是另一篇文章)。无论如何,幕后所有事物的质量都可以为用户带来更高的质量……TagIT

历史

  • 版本 1:发布于 2012 年 3 月 19 日。
© . All rights reserved.