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

使用 VSTO 2008 创建 Outlook 2007 表单区域

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (21投票s)

2007 年 12 月 23 日

CPOL

8分钟阅读

viewsIcon

135448

downloadIcon

2367

在本文中,我们将创建一个窗体区域,用于显示同一类别内的所有联系人。

Screenshot - MyContactsResult.png

引言

在本文中,您将学习如何

  • 构建 VSTO 2008 Outlook 插件
  • 为您的 Outlook 联系人创建窗体区域
  • 使用 Table、Column 和 Row 对象来访问和筛选 MAPIFolder 内容
  • 检索联系人的图片
  • 以编程方式启动即时消息会话

通过使用 VSTO 功能,您可以扩展/替换 Outlook 标准窗体,并用精美的 .NET 用户界面进行扩展。过去,您需要使用 Outlook Forms Designer 来自定义 Outlook 标准窗体。结果是出现一个没有 XP 风格的丑陋旧式窗体。使用 VSTO,您可以利用 Visual Studio 集成的窗体区域设计器来创建具有 .NET 控件的用户界面。

优点

  • Outlook 窗体将保留新设计和新控件(如图片控件)
  • 与您的 Visual Studio 设计器集成
  • 代码模板和调试

概念

对于这个示例,我决定扩展 Outlook 标准联系人窗体,以显示一个包含同一类别内所有联系人的附加页面。在 Outlook 中,您可以为联系人、约会、邮件和任务分配类别。这些项目中的每一个都可以有多个类别,除了主类别列表之外,您还可以定义自己的类别。计划是获取当前联系人,获取其父文件夹,使用 Table 对象,然后查找与选定联系人属于同一类别的所有联系人。如果选定的联系人没有分配任何类别,则简单地显示文件夹中的所有联系人。

解决方案

在您可以创建此解决方案之前,您的开发计算机上需要具备以下先决条件。

必备组件

创建解决方案

此示例是在 Vista Ultimate 64 位系统上使用 Outlook 2007 (德语) 和 Visual Studio 2008 (RTM) 构建的。启动您的 Visual Studio 并创建一个新项目。选择 Office/2007/Outlook Add-in 作为项目模板。

Screenshot - MyContactsCreateProject.png

创建项目后,您会找到一个名为 ThisAddIn 的骨架类,其中有两个方法用于启动和终止应用程序。通常,在这里我会说——我们的应用程序在这里加载……——但这对于窗体区域来说是不正确的。仅供参考,请参阅下面的代码。

namespace MyContacts
{
    public partial class ThisAddIn
    {
        private void ThisAddIn_Startup(object sender, System.EventArgs e)
        {
        }

        private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
        {
        }

        #region VSTO generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InternalStartup()
        {
            this.Startup += new System.EventHandler(ThisAddIn_Startup);
            this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
        }

        #endregion
    }
}

ThisAddIn 类不应被修改。继续,让我们使用 Visual Studio 向导创建一个窗体区域。

创建窗体区域

只需右键单击您的项目并添加新项。选择 Outlook Form Region 作为模板。

Screenshot - MyContactsAddFormRegion.png

在下一个屏幕截图中,您可以看到如何创建窗体区域。首先,选择 Design a new form region (设计一个新窗体区域)。

Screenshot - MyContactsFormRegion1.png

选择您想要哪种类型的窗体区域。对于此示例,请选择 Separate (分离) 类型,这意味着它将显示在一个额外的页面上。

Screenshot - MyContactsFormRegion2.png

为窗体区域命名。

Screenshot - MyContactsFormRegion3.png

最后,选择哪种类型的邮件类应该显示此窗体区域。在此示例中,仅支持联系人窗体。邮件类是 'IPM.Contact'。

Screenshot - MyContactsFormRegion4.png

VSTO 附带的向导现在会为窗体区域创建一个代码模板。每次加载窗体区域时,都会调用 FormRegionShowing 方法。在窗体区域卸载之前,会执行 FormRegionClosed 方法。请参阅下面生成的桩方法。

#region Form Region Factory

[Microsoft.Office.Tools.Outlook.FormRegionMessageClass(
   Microsoft.Office.Tools.Outlook.FormRegionMessageClassAttribute.Contact)]
[Microsoft.Office.Tools.Outlook.FormRegionName("MyContacts.FormRegionMyContacts")]
public partial class FormRegionMyContactsFactory {
    // Occurs before the form region is initialized.
    // To prevent the form region from appearing, set e.Cancel to true.
    // Use e.OutlookItem to get a reference to the current Outlook item.
    private void FormRegionMyContactsFactory_FormRegionInitializing(object sender, 
            Microsoft.Office.Tools.Outlook.FormRegionInitializingEventArgs e) {
    }
}

#endregion

// Occurs before the form region is displayed.
// Use this.OutlookItem to get a reference to the current Outlook item.
// Use this.OutlookFormRegion to get a reference to the form region.
private void FormRegionMyContacts_FormRegionShowing(object sender, System.EventArgs e) {

}

// Occurs when the form region is closed.
// Use this.OutlookItem to get a reference to the current Outlook item.
// Use this.OutlookFormRegion to get a reference to the form region.
private void FormRegionMyContacts_FormRegionClosed(object sender, System.EventArgs e) {

}

现在,当您开始调试解决方案并打开一个联系人窗体时,您会在功能区中注意到一个额外的图标。通过此按钮,您可以访问新的窗体区域。

下面,您可以看到一个带有新按钮的联系人窗体。

Screenshot - MyContactsContactForm.png

在此示例中,我想显示与当前联系人属于同一类别的所有联系人。要实现此目标,您需要访问当前窗体区域的联系人项。在窗体区域类中,您可以通过 this.OutlookItem 访问当前的 Outlook 项。您有一个 ContactItem 类型的项,因此您必须显式地从通用 Outlook 项类型进行转换。联系人的类别可以通过 Categories 属性访问。该值是字符串的连接列表,以分号分隔。例如:当一个联系人被分配了“Outlook”和“Friends”类别时,Categories 的值将是“Outlook; Friends”。如果您想显示所有具有相同类别的其他联系人列表,您需要检索联系人的所需信息并在窗体区域中显示它。对于显示,您可以使用 ListView,因为它易于使用,并且您还可以显示联系人图片。

您想要一个快速响应的应用程序,因此您可以使用 Table 对象来访问 MAPIfolder 的信息。可以使用某种 SQL 语法过滤 Table 的内容。因为您想检索同一类别的所有联系人,所以您需要为 Table 对象构建一个 SQL 过滤器。以下代码行向您展示了如何为类别构建过滤器

/// <summary>
/// Build the SQL Filter for the  
/// </summary>
/// <param name="messageClass">The desired MessageClass</param>
/// <param name="categoryList">A list of categories for the contact</param>
/// <returns>returns the complete filter for the Table-Object</returns>
string BuildSQLFIlter(string messageClass, string[] categoryList) {

    // We can use a StringBuilder object for 
    StringBuilder filter = new StringBuilder(250);
    // SQL prefix
    filter.Append(@"@SQL=(");
    // only types with a specific messageClass
    filter.Append(string.Format(@"(""http://schemas." + 
           "microsoft.com/mapi/proptag/0x001a001e"" = '{0}')", 
           messageClass ));

    // are there categories ? append an AND conjunction
    if (categoryList.Length > 0) filter.Append(" AND (");
    // all categories of the List are ORed together
    for (int index = 0; index < categoryList.Length; index++) {
        if (index > 0) filter.Append(" OR ");
        filter.Append(string.Format(@"(""urn:schemas-microsoft" + 
          "-com:office:office#Keywords"" LIKE '%{0}%')", 
          categoryList[index].Trim()));
    }
    // end bracket for the AND 
    if (categoryList.Length > 0) filter.Append(")");
    // end bracket for the complete SQL
    filter.Append(")");
    return filter.ToString();
}

过滤器的含义是“所有消息类为“IPM.Contact”以及类别 A 或类别 B 或……”的所有类型。

当您访问过滤后的表项时,您必须告诉您想要检索哪些列。这是 MAPI 的本质,您想访问的列越多,从存储中检索数据所需的时间就越长。

您甚至无法通过 Table 对象直接访问图片。所以,这里最好的选择是尽快获取联系人数据,如姓名、电子邮件、主页等,而耗时操作应该在后台处理。当然,您会为此使用 Backgroundworker 对象。Backgroundworker 的工作是获取一个联系人项并从中提取联系人图片——然后将其传递给 ListView 中相应的项。

下面您可以看到用于检索所需数据的完整方法。

// Occurs before the form region is displayed.
// Use this.OutlookItem to get a reference to the current Outlook item.
// Use this.OutlookFormRegion to get a reference to the form region.
private void FormRegionMyContacts_FormRegionShowing(
             object sender, System.EventArgs e) {

    Outlook.ContactItem contact;
    Outlook.MAPIFolder folder;
    Outlook.Table table;

    try {
        // get the current Outlook ContactItem
        contact = this.OutlookItem as Outlook.ContactItem;
        // the contact folder object
        folder = contact.Parent as Outlook.MAPIFolder;

        // retrieve the categories for the current contact
        string categories = contact.Categories ?? string.Empty ;
        string[] categoryList = categories.Split(';');

        // build he SQL filter for the categories
        string filter = BuildSQLFIlter ("IPM.Contact", categoryList );
       
        // get the Table, filltered only for items matching
        // the filter and the MessageClass, no HiddenItems
        table = folder.GetTable(filter.ToString (), 
                Outlook.OlTableContents.olUserItems);

        // define wich columns should be retrieved from table
        table.Columns.RemoveAll();
        table.Columns.Add("EntryID");
        table.Columns.Add("FileAs");
        table.Columns.Add(@"urn:schemas:contacts:profession");
        table.Columns.Add("Email1Address");
        table.Columns.Add(@"urn:schemas:contacts:businesshomepage");

        // the propertytag for the Instant-Messenger address
        table.Columns.Add(@"http://schemas.microsoft.com/mapi/" + 
              @"id/{00062004-0000-0000-C000-000000000046}/8062001F");
        table.Columns.Add("Categories");

        // the MAPI propertytag for the 'HasPicture' flag
        table.Columns.Add(@"http://schemas.microsoft.com/mapi/" + 
              @"id/{04200600-0000-0000-C000-000000000046}/8015000B");

        // create an Imagelist and add the 'NoPicture' image to the list
        ImageList imageList = new ImageList();
        imageList.ImageSize = IMAGESIZE;
        imageList.ColorDepth = ColorDepth.Depth24Bit; 

        // assign the ImageList to the ListView in the form region
        listViewContacts.LargeImageList = imageList;
        // listViewContacts.TileSize = IMAGESIZE;
        imageList.Images.Add(string.Empty  , Properties.Resources.NoPicture);

        // build a List of conatcs which have a picture assigned
        List<string> contactsWithPicture = new List<string>();

        // loop over the contacts and add them to the listView
        // initially the businessCard is filled with the current Info
        while (!table.EndOfTable) {
            Outlook.Row row = table.GetNextRow();
            // fill the COntactInfo from row information
            ContactInfo info = new ContactInfo (row);

            if (info.HasPicture) {
                contactsWithPicture.Add(info.EntryId);
            }                    
            if (contact.EntryID != info.EntryId) {

                ListViewItem listViewItem = 
                  this.listViewContacts.Items.Add(info.FileAs, 0);
                listViewItem.Tag = info;
                listViewItem.Name = info.EntryId;
                listViewItem.ToolTipText = info.EmailAddress;

            } else {
                // display the curent data in the businesscard view
                UpdateBusinessCard(info, GetContactPicture(info.EntryId));
            }
        }

        // the long running operation to retrieve
        // the contact pictures is done in a separate thread
        _backgroundWorker = new BackgroundWorker();
        _backgroundWorker.WorkerSupportsCancellation = true;
        _backgroundWorker.DoWork += new DoWorkEventHandler(_backgroundWorker_DoWork);
        // we pass along a list of contacts with attached pictures
       _backgroundWorker.RunWorkerAsync(contactsWithPicture);

    } finally {
        table = null;
        folder = null;
        contact = null;
    }
}

您了解到无法通过 Table 对象访问联系人图片。以下代码段演示了如何从 Outlook ContactItem 检索联系人图片。当联系人附有图片时,它可以作为普通附件访问,并且有一个默认名称,“ContactPicture.jpg”。

/// <summary>
/// retrieves the picture of a contact
/// </summary>
/// <param name="entryId">the entryId of the contact</param>
/// <returns>returns the image or null</returns>
private Image GetContactPicture(string entryId) {
    // retrieve the contact item by it's entryID
    Outlook.ContactItem contact = 
      Globals.ThisAddIn.Application.Session.GetItemFromID(entryId, 
      null) as Outlook.ContactItem;
    Image img = null;
    // a path to temporarily store the attached picture
    string tempPath = Environment.GetEnvironmentVariable("TEMP");
    if (contact != null) {
        // check for the contact picture
        foreach (Outlook.Attachment attachment in contact.Attachments) {
            if (attachment.FileName == "ContactPicture.jpg") {
                // save the file with a unique name
                string fileName = Path.Combine(tempPath, entryId + ".jpg");
                attachment.SaveAsFile(fileName);
                // read the saved image into a Bitmap using a stream
                FileStream stream = new FileStream(fileName,FileMode.Open );
                Bitmap bmp = new Bitmap(Image.FromStream (stream,true ));
                // check the aspect-ratio of the picture
                if (bmp.Width >= bmp.Height) {
                    // scale Image
                    Bitmap tempBmp = new Bitmap(IMAGESIZE.Width ,IMAGESIZE.Height  );
                    Graphics g = Graphics.FromImage (tempBmp );
                    g.FillRectangle(Brushes.White,0, 0, IMAGESIZE.Width, IMAGESIZE.Height);  
                    
                    float ratio = (float)bmp.Height  / bmp.Width  ;
                    int newHeight = (int)(ratio * bmp.Height);

                    // draw the scaled Image onto the empty Bitmap
                    int top = (IMAGESIZE.Height - newHeight) / 2;
                    g.DrawImage (bmp, new Rectangle (0,top, IMAGESIZE.Width , newHeight )); 
                    img = tempBmp;
                    g.Dispose ();
                } else {
                    // resize the picture
                    img = new Bitmap(bmp, IMAGESIZE);
                }
                stream.Dispose ();       
                // delete the temp file
                File.Delete(fileName);
                break;
            }
        }
    }
    contact = null;
    return img;
}

对于每个有图片的联系人,图像将在后台工作线程中检索。线程应更新用户界面。通常,当从 UI 线程以外的线程访问控件时,您必须使用控件的 Invoke 方法。在下面的代码块中,您将看到图片如何从工作线程在窗体区域中更新。

/// <summary>
/// does all the background work
/// in this case, it get's the Images for the contacts and updates the imagelist 
/// </summary>
/// <param name="sender">the backgroundworker-instace</param>
/// <param name="e">a startparameter which has been passed to the workerthread</param>
void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e) {

    // we passed the list of contacts with assigned pictures
    List<string> contactsWithPicture = (List<string>)e.Argument;

    // loop over the list, and retrieve the 
    foreach (string entryId in contactsWithPicture) {
        if (e.Cancel) break;
        Image contactPicture = GetContactPicture(entryId);
        if (contactPicture != null) {
            SetImagePicture(entryId, contactPicture);
        }
    }
}


/// <summary>
/// defines a method signature used by the invoke command 
/// </summary>
/// <param name="enrtyId">entryId of the contact</param>
/// <param name="image">image for the contact</param>
public delegate void SetImagePictureDelegate(string enrtyId, Image image);

/// <summary>
/// updates the contact picture in the listview
/// when this method is called from another thread - it will invoke itself
/// </summary>
/// <param name="enrtyId">entryId of the contact</param>
/// <param name="image">image for the contact</param>
public void SetImagePicture(string entryId, Image image) {

    // if the listview needs invocation, call this
    // method again using the listviews invoke method
    if (listViewContacts.InvokeRequired) {
        listViewContacts.Invoke(new SetImagePictureDelegate(
           SetImagePicture), new object[] { entryId, image });
    } else {

        // if the listview has no item with
        // the scpecific ID, everything is sensless
        if (!listViewContacts.Items.ContainsKey(entryId)) return;

        // get the index of the contactimage from the listview imagelist
        int index = listViewContacts.LargeImageList.Images.IndexOfKey(entryId);

        // when there is no such image, add it to the list
        if (index == -1) {
            listViewContacts.LargeImageList.Images.Add(image);
            index = listViewContacts.LargeImageList.Images.Count-1;
        } else {
            listViewContacts.LargeImageList.Images[index] = image;
        }

        // tell the listviewItem that it should use the given image index
        listViewContacts.Items[entryId].ImageIndex = index;

        // get the index of the listviewitem and force a redraw
        index = listViewContacts.Items[entryId].Index ;
        listViewContacts.RedrawItems(index, index, false);
    }
}

/// <summary>
/// backgroundworker is used to update the pictures of the contacts
/// </summary>
private BackgroundWorker _backgroundWorker;

// Occurs when the form region is closed.
// Use this.OutlookItem to get a reference to the current Outlook item.
// Use this.OutlookFormRegion to get a reference to the form region.
private void FormRegionMyContacts_FormRegionClosed(object sender, 
                                  System.EventArgs e) {
    if (_backgroundWorker.IsBusy) {
        _backgroundWorker.CancelAsync();
    }
    _backgroundWorker.Dispose();
}

如上所示,当窗体区域卸载时,必须停止 BackgroundWorker。其余的就是直接编码。只是为了稍微玩一下,我实现了一个小型名片,当鼠标悬停在列表中的联系人上时,会用联系人信息进行更新。双击列表项时,将在 Outlook 中显示相应的联系人。

/// <summary>
/// event sink for the listviewitem doubleclick.
/// used to open the selected contact.
/// </summary>
private void listViewContacts_DoubleClick(object sender, EventArgs e) {
    Point position = listViewContacts.PointToClient(Control.MousePosition);
    ListViewItem listViewItem = 
       listViewContacts.GetItemAt(position.X ,position.Y );
    if (listViewItem == null) return;

    OpenItem(listViewItem.Name); 
}

/// <summary>
/// opens the contact
/// </summary>
/// <param name="entryId">entryId of the contact which shoul be displayed</param>
private void OpenItem(string entryId){
    Outlook.ContactItem contact = 
      Globals.ThisAddIn.Application.Session.GetItemFromID(entryId, null) 
      as Outlook.ContactItem;
    contact.Display(false);
    contact = null;
}

/// <summary>
/// Occurs when the mouse hovers over an Item
/// </summary>
/// <param name="sender">The ListviewItem object</param>
/// <param name="e">The Item where the mouse is hovering</param>
private void listViewContacts_ItemMouseHover(object sender, 
                        ListViewItemMouseHoverEventArgs e) {
    UpdateBusinessCard((ContactInfo)e.Item.Tag, 
       listViewContacts.LargeImageList.Images[e.Item.ImageIndex]);
}

传递给名片的信息封装在一个简单的值持有者类 ContactInfo 中。这是 ContactInfo

public class ContactInfo {

    /// <summary>
    /// The constructor takes an Outlook Row
    /// and parses the Information into the values
    /// </summary>
    /// <param name="row">the outlook row object</param>
    public ContactInfo(Outlook.Row row) {
        EntryId = (string)row[1];
        FileAs = (string)row[2];
        JobTitle = (string)(row[3] ?? string.Empty);
        EmailAddress = (string)(row[4] ?? string.Empty);
        Homepage  = (string)(row[5] ?? string.Empty);
        MessengerAddress  = (string)(row[6] ?? string.Empty);
        string categories = (string)(row[7] ?? string.Empty);
        Categories = categories.Split(';');
        HasPicture = (bool)(row[8] ?? false);
    }

    public string EntryId {get; private set; }
    public string FileAs { get; private set; }
    public string JobTitle { get; private set; }
    public string EmailAddress { get; private set; }
    public string Homepage { get; private set; }
    public string MessengerAddress { get; private set; }
    public string[] Categories { get; private set; }
    public bool HasPicture { get; private set; }
}

如您所见,您可以像设计任何其他 .NET 控件一样,使用 Visual Studio 设计器设计窗体区域。

Screenshot - MyContactsFormDesign.png

作为一项附加功能,您可以看到如何通过使用本机 COM API 使用 Windows Messenger(已在 MSN Live Messenger 上测试)向某人发送即时消息。您还可以单击“Email”并撰写一封新电子邮件,将该联系人作为收件人,或者在 Web 浏览器中显示其主页。您甚至可以双击名片并显示 Outlook 联系人。

请参阅下面的名片代码片段。

/// <summary>
/// fills the BusinessCard with the given ContactInfo
/// </summary>
/// <param name="info">the ContactInfo object.</param>
/// <param name="image" >the contact image</param>
public void SetContactInfo( ContactInfo info , Image image){
    EntryId = info.EntryId;  
    LastFirst.Text = info.FileAs;        
    Profession.Text = info.JobTitle;
    Emailaddress.Text = info.EmailAddress;  
    Messenger.Text = info.MessengerAddress;        
    Homepage.Text = info.Homepage;       
    Categories.Text = string.Join("\n", info.Categories);        
    Image.Image = image;    
}


/// <summary>
/// The EntryId identifies the Outlook ContactItem
/// </summary>
public string EntryId { get; set; }

/// <summary>
/// event sink for the business card doubleclick event
/// </summary>
private void FormBusinessCard_MouseDoubleClick(object sender, MouseEventArgs e) {
    OpenItem(EntryId);
}


/// <summary>
/// retrieves the contact from application context and displays it
/// </summary>
/// <param name="entryId">the entryId of the contact</param>
void OpenItem(string entryId) {
    Outlook.ContactItem contact = 
      Globals.ThisAddIn.Application.Session.GetItemFromID (entryId , 
      null) as Outlook.ContactItem ;
    contact.Display(false);
    contact = null;
}


/// <summary>
/// event sink for the homepage link clicked event
/// </summary>
private void Homepage_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) {
    ShowHomePageInBrowser(Homepage.Text);
}

/// <summary>
/// displays the contacts homepage in a browser
/// </summary>
/// <param name="url">the homepage url</param>
void ShowHomePageInBrowser(string url){
    Process p = new Process();
    p.StartInfo.FileName = "IExplore.exe";
    p.StartInfo.Arguments = url;
    p.Start();
}


/// <summary>
/// event sink for the messenger lnk clicked event.
/// </summary>
private void Messenger_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) {
    ConnectToMessenger(Messenger.Text);
}

/// <summary>
/// Takes the email from the InstantMessaging partner and opens the InstantMessage Window.
/// </summary>
/// <param name="email">the emailaddress of the partner</param>
void ConnectToMessenger(string email) {
    try{
        // create a COM - instance to the Messenger
        Type messengerType = Type.GetTypeFromProgID ("Messenger.UIAutomation.1");
        object comObject = Activator.CreateInstance (messengerType);
        
        // call the InstantMessage method with the emailaddress of the user.
        object[] arguments = new object[] { email };
        messengerType.InvokeMember ("InstantMessage", 
          BindingFlags.InvokeMethod,null, comObject, arguments);
        Marshal.ReleaseComObject(comObject); 


    } catch(System.Exception ex){
        MessageBox.Show("Please make sure you have installed the" + 
          " latest Windows Messenger Live and  that you are signed-in." );
    }
}

/// <summary>
/// event sink for the email address link clicked event
/// </summary>
private void Emailaddress_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) {
    SendEmail(LastFirst.Text, Emailaddress.Text);
}

/// <summary>
/// creates a new Email for the recipient using the emailaddress
/// </summary>
/// <param name="emailAddress">the contacts emailaddress</param>
void SendEmail(string name,string emailAddress) {
    Outlook.MailItem mail =  Globals.ThisAddIn.Application.CreateItem(
      Microsoft.Office.Interop.Outlook.OlItemType.olMailItem) as Outlook.MailItem ;
    mail.Recipients.Add (string.Format("{0}<{1}>", name, emailAddress  ));
    mail.To = emailAddress;
    mail.Display(false);
    mail = null;
}

目前就是这样。去玩玩窗体区域吧,看看使用 VSTO 技术和可视化设计器扩展 Outlook 窗体和用户界面是多么容易。一如既往——来自慕尼黑/德国的问候,希望您喜欢这篇文章。

恢复

  • 构建 VSTO 2008 Outlook 插件
  • 为您的 Outlook 联系人创建窗体区域
  • 使用 Table、Column 和 Row 对象来访问和筛选 MAPIFolder 内容
  • 检索联系人的图片
  • 以编程方式启动即时消息会话

注释

以下注意事项适用于此以及所有 VSTO 插件

  • 此解决方案的临时密钥是在我的开发计算机上创建的。您必须创建并使用自己的密钥。
  • 该解决方案没有安装项目。有关分发 VSTO 插件的信息,请参阅 Deploying VSTO Solutions
  • 对于您的插件使用的每个 DLL,您都必须设置安全策略(MSI 安装包中的自定义操作)。
  • VSTO 2008 Beta2 和 VSTO 2008 RTM 解决方案不兼容!!!您必须修补 .csproj 文件才能使其正常工作。

特别感谢(您将在他们的网站上找到大量关于使用和编程 Outlook、CDO 和 Exchange 的信息。没有他们的帮助,我不会是今天的我)

历史

  • V.1.0 - 初始版本(2007 年 12 月 23 日)
  • V.1.1 - 添加了 VB.NET 解决方案(2008 年 1 月 1 日)
© . All rights reserved.