What's New for Developers in Outlook 2010
Summary: This article presents a top-level view of the enhancements and additions for developers in Microsoft Outlook 2010. It also examines, in depth, some of the new and enhanced objects in the Outlook object model, including their properties, methods, and events. For developers who are eager to learn the Outlook platform, this article provides sufficient detail to begin coding against Outlook 2010.
Applies to: Office 2010 | Outlook 2010
In this article
Outlook 2010 Platform Overview
Outlook Object Model Changes
Add-in Resiliency
Improved UI Extensibility
Comprehensive Object Model
Support for 32-Bit and 64-Bit Platforms
Multiple Exchange Accounts
Conclusion
Additional Resources
Published: November 2009 | Updated: May 2010
Provided by: Randy Byrne, Microsoft Corporation
Contents
Outlook 2010 Platform Overview
Outlook Object Model Changes
Add-in Resiliency
Improved UI Extensibility
Comprehensive Object Model
Support for 32-Bit and 64-Bit Platforms
Multiple Exchange Accounts
Conclusion
Additional Resources
Outlook 2010 Platform Overview
The changes to the Outlook 2010 object model satisfy customer requests to be able to extend the user interface (UI) and provide support in the object model for the new features that were designed for the Outlook 2010 end user. The Outlook 2010 platform includes the following features:
Add-in resiliency
Add-ins should not degrade Outlook performance or resiliency. Outlook writes a list of connected add-ins to the Windows event log and records add-in boot time for each connected add-in. Additionally, Outlook writes to the Windows event log when an add-in crashes during an event callback. To prevent add-ins from adversely affecting the performance of Outlook when a user action closes the Outlook application, Outlook uses a new fast shutdown process. For more information about the new shutdown process, see Shutdown Changes for Outlook 2010.
Improved UI extensibility
You can customize many new areas of the Outlook UI, including a custom navigation module called the Solutions module in the Navigation Pane. You can set or get custom folder icons for Outlook folders under the Solutions module. The Solutions module makes it easier to find the contents of a custom solution folder. By using extensibility for the Microsoft Office Fluent user interface, you can customize areas of the UI that were not extensible in previous versions of Outlook. For more information about the Solutions module, see Programming the Outlook 2010 Solutions Module. For more information about how to use ribbon extensibility to customize the Outlook UI, see Extending the User Interface in Outlook 2010.
32-bit and 64-bit platform support
You can develop solutions for 32-bit and 64-bit platforms by using one API, the Outlook object model.
Comprehensive object model
For the great majority of developers, the object model is comprehensive and sufficient to write a professional solution without the need to write code at the Messaging API (MAPI) level.
Multiple Exchange accounts
You can define multiple Microsoft Exchange accounts under a single profile. The object model was originally designed with the assumption that only one Exchange account would run in a single profile. The updated object model is improved to handle multiple Exchange accounts.
This article discusses important new features in the object model and includes code examples in C# that can help you to write new solutions or adapt existing code for Outlook 2010.
Note
The objects, properties, methods, and events in this article might change in the final release of Outlook 2010, and additional features might be introduced, as well. Be sure that you test your code changes in the final version of Outlook 2010 before you release your solution.
Outlook Object Model Changes
The Outlook object model has new objects, methods, properties, and events that support new Outlook 2010 features programmatically. Other improvements to the object model address frequent developer requests for specific changes to the Outlook platform.
Enhancements to Existing Outlook Objects and Collections for Outlook 2010
The following table lists enhancements to objects and collections that are in earlier versions of Outlook. The second column lists only new methods, properties, and events.
Table 1. Outlook object model enhancements
Objects and collections |
New members |
Methods Properties |
|
Events |
|
Methods RefreshFormRegionDefinition(String) Properties |
|
Methods CopyTo(MAPIFolder, OlAppointmentCopyOptions) Properties Events |
|
Methods |
|
Methods GetSelection(OlSelectionContents) Properties |
|
Properties |
|
Methods Properties Events AfterWrite BeforeRead |
|
Methods Properties |
|
Events AfterWrite BeforeRead |
|
Methods |
|
Methods IsItemSelectableInView(Object) Properties Events |
|
Methods |
|
Properties |
|
Methods SetSchedulingStartTime(DateTime) Properties Events |
|
Methods Properties Events AfterWrite BeforeRead |
|
Methods Properties Events AfterWrite BeforeRead |
|
Methods Properties Events AfterWrite BeforeRead |
|
Methods |
|
Methods Properties Events AfterWrite BeforeRead |
|
Properties |
|
Methods Properties Events AfterWrite BeforeRead |
|
Methods Properties Events AfterWrite BeforeRead |
|
Methods GetSelection(OlSelectionContents) Properties |
|
Methods Properties Events AfterWrite BeforeRead |
|
Methods GetDefaultFolder(OlDefaultFolders) Properties |
|
Methods Properties |
|
TaskRequestItem, and |
Methods GetConversation Properties ConversationID RTFBody |
Properties |
New Outlook Objects and Collections for Outlook 2010
The following table lists the new objects introduced in Outlook 2010. All object members are listed in the second column.
Table 2. Outlook object model additions
Objects |
Methods, properties, and events |
Properties Events |
|
Methods ClearAlwaysAssignCategories(Store) GetAlwaysAssignCategories(Store) SetAlwaysAssignCategories(String, Store) SetAlwaysDelete(OlAlwaysDeleteConversation, Store) SetAlwaysMoveToFolder(MAPIFolder, Store) Properties |
|
Methods Properties |
|
Methods Properties Events AfterWrite BeforeAttachmentWriteToTempFile BeforeRead |
|
Properties |
|
Methods AddSolution(MAPIFolder, OlSolutionScope) Properties |
Add-in Resiliency
Add-in resiliency is an important feature of the Outlook 2010 platform. Add-in resiliency means that before and after loading an add-in, Outlook continues to perform and respond normally. Add-ins run in-process to Outlook and can potentially degrade the Outlook user experience. In addition, IT administrators are concerned that in unmanaged environments they do not always know which add-ins users are running, and poorly written add-ins contribute to help-desk calls and the cost of ownership. Because all Outlook object model calls execute on the main foreground thread in Outlook, it is important that you write your code with performance as a paramount goal. If your code makes Outlook run slowly or suspends it indefinitely, users will be frustrated and abandon your solution.
Add-in Inventory and Boot Time in the Windows Event Log
Outlook 2010 gives users and IT administrators the ability to determine which add-ins load when Outlook starts, and the impact that those add-ins have on that startup time. When Outlook starts up, detailed information about the identity of each connected add-in is written to the Windows event log. In addition, the boot time for each connected add-in is written to the event log in milliseconds.
Figure 1. Outlook 2010 add-ins and boot time per add-in in the Windows event log
Add-in Fast Shutdown
Outlook 2010 enforces a new fast shutdown process for add-ins. The new shutdown process prevents add-ins from causing long delays by holding on to resources after the user exits Outlook. Although this change could adversely affect a small number of existing add-ins, add-in vendors and IT administrators can mitigate those effects by forcing Outlook to revert to the standard add-in shutdown process.
The following list describes some key facts about the fast shutdown process for add-ins:
When a user action shuts down Outlook, the OnDisconnection event of IDTExtensibility2 interface will not occur when the RemoveMode parameter is set to ext_DisconnectMode.ext_dm_HostShutdown.
For managed Microsoft Visual Studio Tools for Office add-ins, the Shutdown method of the ThisAddin class will not be called when a user action shuts down Outlook.
When a user action in the COM Add-ins dialog box disconnects the add-in, the OnDisconnection event of the IDTExtensiblity2 interface occurs when RemoveMode equals ext_DisconnectMode.ext_dm_UserClosed.
When a user action shuts down Outlook, the Quit event of the Outlook Application object occurs.
For more information about add-in shutdown and best practices, see Shutdown Changes for Outlook 2010.
Improved UI Extensibility
Outlook 2010 provides the following ways to extend the UI that developers have often requested:
Add a hierarchy of custom folders to the new Solutions module in the Navigation Pane. In the Outlook object model, the Solutions module is represented by the SolutionsModule object. The SolutionsModule object exposes the AddSolution method that you can use to add a solution root folder. Outlook 2010 automatically adds all subfolders of the solution root folder to the solution folder hierarchy that is displayed in the Solutions module. See Figure 2 for an example of the Solutions module.
Customize folder icons for solution folders. You can use the SetCustomIcon and GetCustomIcon methods of the Folder object to set and get folder icons.
Use Office Fluent UI extensibility to customize the Outlook UI, including the following:
Explorer ribbons
Inspector ribbons
Context menus
New item menus
Contact Card context menus
Contextual tabs
Microsoft Office Backstage view
Figure 2. Example Solutions module in Outlook 2010
If your existing code customizes Office command bars, those UI customizations appear in the Add-Ins tab, which users might not easily find. Follow the Office Fluent UI extensibility guidelines and rewrite your code to take advantage of Explorer ribbons, Office Fluent UI context menus, and Backstage view. For comprehensive information about UI extensibility in Outlook 2010, as well as sample code, see the following articles:
Comprehensive Object Model
The goal of a comprehensive object model is to ensure that developers can write professional solutions without using MAPI code directly. In managed code, you cannot develop Outlook solutions that use MAPI.
Object Model for Conversations
Outlook 2010 introduces a powerful new feature called the conversation view, and a new object called the Conversation object. You can use the Conversation object to execute conversation verbs and to traverse the conversation tree. Items in the conversation can be in different stores and folders.
To access items in a conversation thread, call the GetConversation method on an item to return a Conversation object. The Conversation object represents a conversation to which the parent item belongs.
GetConversation returns Null (Nothing in Visual Basic) if no conversation exists for the item. No conversation exists for an item under the following conditions:
The item has not been saved. An item can be saved programmatically, by user action, or by AutoSave.
If an item can be sent, but has not been sent (for example, a mail item, appointment item, or contact item).
All conversations have been disabled through the Windows registry.
The store does not support conversations (for example, when Outlook is running in classic online mode against a version of Microsoft Exchange that is earlier than Microsoft Exchange Server 2010). Use the IsConversationEnabled property of the Store object to determine whether the store supports conversations.
The following DemoConversation function obtains the Conversation object for a selected item in the Outlook explorer window. To list items in a conversation, use the GetTable method on the Conversation object to return a Table object. You can then add columns, if necessary, to the Table object, or call GetNextRow to return each row in the Table.
void DemoConversation()
{
object selectedItem = Application.ActiveExplorer().Selection[1];
// For this example, you work only with
// MailItem. Other item types such as
// MeetingItem and PostItem can participate
// in a conversation.
if (selectedItem is Outlook.MailItem)
{
// Cast selectedItem to MailItem.
Outlook.MailItem mailItem =
selectedItem as Outlook.MailItem; ;
// Determine store of MailItem.
Outlook.Folder folder = mailItem.Parent
as Outlook.Folder;
Outlook.Store store = folder.Store;
if (store.IsConversationEnabled == true)
{
// Obtain a Conversation object.
Outlook.Conversation conv =
mailItem.GetConversation();
// Check for null conversation.
if (conv != null)
{
// Obtain Table that contains rows
// for each item in the conversation.
Outlook.Table table = conv.GetTable();
Debug.WriteLine("Conversation Items Count: " +
table.GetRowCount().ToString());
Debug.WriteLine("Conversation Items from Table:");
while (!table.EndOfTable)
{
Outlook.Row nextRow = table.GetNextRow();
Debug.WriteLine(nextRow["Subject"]
+ " Modified: "
+ nextRow["LastModificationTime"]);
}
Debug.WriteLine("Conversation Items from Root:");
// Obtain root items and enumerate Conversation.
Outlook.SimpleItems simpleItems
= conv.GetRootItems();
foreach (object item in simpleItems)
{
// In this example, enumerate only the MailItem type.
// Other types such as PostItem or MeetingItem
// can appear in a conversation.
if (item is Outlook.MailItem)
{
Outlook.MailItem mail = item
as Outlook.MailItem;
Outlook.Folder inFolder =
mail.Parent as Outlook.Folder;
string msg = mail.Subject
+ " in folder " + inFolder.Name;
Debug.WriteLine(msg);
}
// Call EnumerateConversation
// to access child nodes of root items.
EnumerateConversation(item, conv);
}
}
}
}
}
void EnumerateConversation(object item,
Outlook.Conversation conversation)
{
Outlook.SimpleItems items =
conversation.GetChildren(item);
if (items.Count > 0)
{
foreach (object myItem in items)
{
// In this example, enumerate only MailItem type.
// Other types such as PostItem or MeetingItem
// can appear in a conversation.
if (myItem is Outlook.MailItem)
{
Outlook.MailItem mailItem =
myItem as Outlook.MailItem;
Outlook.Folder inFolder =
mailItem.Parent as Outlook.Folder;
string msg = mailItem.Subject
+ " in folder " + inFolder.Name;
Debug.WriteLine(msg);
}
// Continue recursion.
EnumerateConversation(myItem, conversation);
}
}
}
If you want to navigate each node of the conversation, an alternative approach is to call the GetRootItems method to return one or more root items of the conversation. A conversation can have multiple root items if the original single root item was deleted, and child items were promoted to become root items. Once you have the root items, you can obtain the SimpleItems collection for all child nodes of each root item. SimpleItems is a collection object that you can use to enumerate the child items for each node in the conversation. When you use a foreach loop to enumerate each item in the collection, call the EnumerateConversation function recursively to access each and every child item in the conversation. The EnumerateConversation function accepts two parameters, one that represents an item such as a MailItem, and a second that represents a Conversation object. The GetChildren method of the Conversation object returns a SimpleItems collection that represents the child nodes of a given item in the conversation. If SimpleItems.Count is greater than zero, the enumeration of child nodes proceeds and EnumerateConversation is called on each child node.
The Conversation object also exposes methods that allow conversation verbs to be applied to a conversation. Specifically, you can use the following methods on the Conversation object in Outlook 2010.
Table 3. Conversation object methods
Method |
Description |
ClearAlwaysAssignCategories |
Removes all categories from all items in the conversation and stops Outlook from always assigning categories to items in the conversation. |
GetAlwaysAssignCategories |
Returns a String that indicates the category or categories that are assigned to all new items that arrive in the conversation. |
GetAlwaysDelete |
Returns a constant in the OlAlwaysDeleteConversation enumeration that indicates whether all new items that arrive in the conversation are always moved to the Deleted Items folder in the specified delivery store. |
GetAlwaysMoveToFolder |
Gets a Folder object in the specified delivery store to which all new items that arrive in the conversation are always moved. |
MarkAsRead |
Marks all items in the conversation as read. |
MarkAsUnread |
Marks all items in the conversation as unread. |
SetAlwaysAssignCategories |
Applies one or more categories to all existing items and future items of the conversation. |
SetAlwaysDelete |
Sets a constant in the OlAlwaysDeleteConversation enumeration that indicates whether all existing items and all new items that arrive in the conversation are always moved to the Deleted Items folder in the specified delivery store. |
SetAlwaysMoveToFolder |
Sets a Folder object in the specified delivery store to which all existing items and all new items that arrive in the conversation are always moved. |
StopAlwaysDelete |
Stops the action of always moving conversation items on the specified store to the Deleted Items folder on that store. |
StopAlwaysMoveToFolder |
Stops the action of always moving conversation items on the specified store to a specific folder. |
For example, the ignore action causes all current and future items in the conversation to be moved to the Deleted Items folder. To write code that duplicates the ignore action, use the SetAlwaysDelete method to always delete all items in the conversation and move any new conversation items to the Deleted Items folder. To reverse the ignore action programmatically, call the StopAlwaysDelete method. The following code example illustrates the use of the SetAlwaysDelete method.
void DemoIgnoreConversation()
{
// Obtain the selection.
object selectedItem =
Application.ActiveExplorer().Selection[1];
if (selectedItem is Outlook.MailItem)
{
// Cast the object to MailItem.
Outlook.MailItem mail = selectedItem
as Outlook.MailItem;
// Determine the store of mail.
Outlook.Folder folder = mail.Parent
as Outlook.Folder;
Outlook.Store store = folder.Store;
if (store.IsConversationEnabled == true)
{
Outlook.Folder delItems =
store.GetDefaultFolder(
Outlook.OlDefaultFolders.olFolderDeletedItems)
as Outlook.Folder;
Outlook.Conversation conv = mail.GetConversation();
if (conv != null)
{
conv.SetAlwaysDelete(
Outlook.OlAlwaysDeleteConversation.olAlwaysDelete,
delItems.Store);
}
}
}
}
Form Region Enhancements
There are a few enhancements to form regions in this release.
Custom Fields for Form Regions
You can define custom fields for form regions in the form region XML manifest, using the new fieldRegistry and userField elements. Outlook loads this information when Outlook starts, and fields in the form regions are available and initialized with their default values when an item is opened. A custom field defined for a form region is a UserProperty object for an item of the same message class specified by the form region.
To specify custom fields for form regions, you must define the fields in the form region XML manifest, regardless of whether you bind controls to these fields at design time or run time. That is, if you define custom fields by using the Field Chooser dialog box and bind controls to these custom fields at design time, you must also define the same custom fields in the form region XML manifest. If you are binding controls at run time, you should first define the custom fields in the form region XML manifest file, and then bind controls to these fields during the BeforeFormRegionShow event.
Because the same custom field can be defined more than once, in the form region XML manifest and in the Field Chooser, if there are any discrepancies in the field definitions, Outlook always uses the definition in the form region XML manifest.
Refresh Form Region Data at Run Time
You can now call the RefreshFormRegionDefinition method of the Application object to refresh the cache and obtain the current definition for an existing form region or all existing form regions defined for the local computer and the current user. This includes reloading the form region XML manifest for the specified form region or form regions.
Show or Hide a Form Region at Run Time
An add-in can show or hide an adjoining or separate form region at run time. You can set the new defaultVisibility element in the form region XML manifest for Outlook to show or hide the form region by default. You can use the ShowFormPage and HideFormPage methods of the Inspector object to show or hide a form region, even when an item is open and is using that form. Assuming the following declarations in C#, the subsequent two code blocks show how to display in an inspector the form region specified by the InternalName property of myFormRegion, and how to hide the form region in the inspector.
Declarations:
using Outlook = Microsoft.Office.Interop.Outlook;
Outlook.Inspector myInspector;
Outlook.FormRegion myFormRegion;
Display the form region:
myInspector.ShowFormPage (myFormRegion.InternalName);
Hide the form region:
myInspector.HideFormPage (myFormRegion.InternalName);
You can use the new Visible property of the FormRegion object to display or hide a form region, and control access to information without displaying it in a form region.
Note
To hide or show a form region, the add-in must be the implementer of the form region.
Set a Form Region as the Current Form Page
The SetCurrentFormPage method of the Inspector object has been enhanced to allow an add-in to set a separate, replace, or replace-all form region as the current form page.
Display Form Regions Only for Connected Add-ins
When a user disconnects an add-in, Outlook no longer displays the custom user interface that the add-in has defined in its form regions. If the add-in is later connected, that add-in’s form regions are visible again the next time an item opens in an inspector or the Reading Pane.
Obtaining the Sender's SMTP Address
In Microsoft Office Outlook 2007, developers used the PropertyAccessor object in code similar to the following to obtain the sender’s SMTP address.
private string GetSenderSMTPAddress(Outlook.MailItem mail)
{
if (mail == null)
{
throw new ArgumentNullException();
}
string PR_SENT_REPRESENTING_ENTRYID =
@"https://schemas.microsoft.com/mapi/proptag/0x00410102";
string PR_SMTP_ADDRESS =
@"https://schemas.microsoft.com/mapi/proptag/0x39FE001E";
if (mail.SenderEmailType == "EX")
{
string senderEntryID =
mail.PropertyAccessor.BinaryToString(
mail.PropertyAccessor.GetProperty(
PR_SENT_REPRESENTING_ENTRYID));
Outlook.AddressEntry sender =
Application.Session.
GetAddressEntryFromID(senderEntryID);
if (sender != null)
{
// Now there is an AddressEntry that represents the sender.
if (sender.AddressEntryUserType ==
Outlook.OlAddressEntryUserType.
olExchangeUserAddressEntry
|| sender.AddressEntryUserType ==
Outlook.OlAddressEntryUserType.
olExchangeRemoteUserAddressEntry)
{
// Use the PrimarySMTPAddress property of the
// ExchangeUser object.
Outlook.ExchangeUser exchUser =
sender.GetExchangeUser();
if (exchUser != null)
{
return exchUser.PrimarySmtpAddress;
}
else
{
return null;
}
}
else
{
return sender.PropertyAccessor.GetProperty(
PR_SMTP_ADDRESS) as string;
}
}
else
{
return null;
}
}
else
{
return mail.SenderEmailAddress;
}
}
In Outlook 2010, the Sender property of the MailItem object returns or sets an AddressEntry object that represents the sender of the item. You no longer must use the PropertyAccessor object to determine the PR_SENT_REPRESENTING_ENTRYID (PidTagSentRepresentingEntryId) property. Instead, you can write code that is similar to the following example.
private string GetSenderSMTPAddress2010(Outlook.MailItem mail)
{
string PR_SMTP_ADDRESS =
@"https://schemas.microsoft.com/mapi/proptag/0x39FE001E";
if (mail == null)
{
throw new ArgumentNullException();
}
if (mail.SenderEmailType == "EX")
{
Outlook.AddressEntry sender =
mail.Sender;
if (sender != null)
{
// Now there is an AddressEntry that represents the sender.
if (sender.AddressEntryUserType ==
Outlook.OlAddressEntryUserType.
olExchangeUserAddressEntry
|| sender.AddressEntryUserType ==
Outlook.OlAddressEntryUserType.
olExchangeRemoteUserAddressEntry)
{
// Use the PrimarySMTPAddress property of the
// ExchangeUser object.
Outlook.ExchangeUser exchUser =
sender.GetExchangeUser();
if (exchUser != null)
{
return exchUser.PrimarySmtpAddress;
}
else
{
return null;
}
}
else
{
return sender.PropertyAccessor.GetProperty(
PR_SMTP_ADDRESS) as string;
}
}
else
{
return null;
}
}
else
{
return mail.SenderEmailAddress;
}
}
PropertyAccessor Improvements
Use the PropertyAccessor object to create, set, get, and delete properties on objects. In Outlook 2007, the PropertyAccessor object imposed limitations on the size of the property that could be retrieved with a GetProperty or GetProperties call. In Outlook 2010, those limitations have been removed.
You can also use the PropertyAccessor object in Outlook 2010 to manipulate attachments without writing the attachment to disk by using the SaveAsFile method on the Attachment object. The following code example uses the PropertyAccessor object to retrieve a byte array from the Attachment object, change the byte array from A to B in memory, and then set the Attachment object to the changed byte array. To persist changes, call the Save method on the item.
private void DemoAttachmentStream()
{
const string PR_ATTACH_DATA_BIN =
"https://schemas.microsoft.com/mapi/proptag/0x37010102";
// Create a mail item.
Outlook.MailItem mail =
Application.CreateItem(Outlook.OlItemType.olMailItem)
as Outlook.MailItem;
mail.Subject = "Demo Attachment Stream";
// Create the c:\demo folder if it does not exist.
if(!Directory.Exists(@"c:\demo"))
{
Directory.CreateDirectory(@"c:\demo");
}
// Write to the attach.txt file.
StreamWriter sw = new StreamWriter(@"c:\demo\attach.txt");
char charA = 'A';
string myString = new string(charA ,4096);
sw.WriteLine(myString);
sw.Close();
// Add attach.txt as an attachment.
Outlook.Attachment attach =
mail.Attachments.Add(@"c:\demo\attach.txt",
Outlook.OlAttachmentType.olByValue,
Type.Missing,
Type.Missing);
// Save the item.
mail.Save();
// Use PropertyAccessor to retrieve attachment byte stream.
byte[] attachStream =
attach.PropertyAccessor.GetProperty(
PR_ATTACH_DATA_BIN) as byte[];
// Iterate the stream and change "A" to "B".
for (int i = 0; i < attachStream.Length; i++)
{
attachStream[i] = 0x42; //Hex for "B"
}
// Set PR_ATTACH_DATA_BIN to attachStream.
attach.PropertyAccessor.SetProperty(PR_ATTACH_DATA_BIN,
attachStream);
// Save the item again.
mail.Save();
}
Accessing Rich Text Formats (RTF)
Outlook supports access to rich formatting for the body of an item through the WordEditor property of the Inspector object. WordEditor represents the Word.Document object in the Microsoft Word object model. Inspector.WordEditor works well in cases where an Outlook item is displayed in an inspector. For cases where the inspector window is not displayed, Outlook 2010 introduces the RTFBody property on an item object such as MailItem, AppointmentItem, and so on. RTFBody is exposed on all item types except for NoteItem and JournalItem. RTFBody is a read/write property, and you can set or get a byte array that represents the RTF stream for the item. Depending on your development environment, you must convert the byte array returned by RTFBody to a string, modify the string that contains the RTF, if necessary, convert the string to a byte array, and then set the RTFBody to the modified byte array.
The following code example writes the RTF for the first item in the Selection object for the active explorer window to the Debug trace listeners window in Visual Studio. The code obtains the byte array from the mail object, and then uses System.Text.AsciiEncoding to convert the byte array to a string.
private void GetRTFBodyForMail()
{
Outlook.Selection selection =
Application.ActiveExplorer().Selection;
if(selection.Count >= 1)
{
if (selection[1] is Outlook.MailItem)
{
Outlook.MailItem mail =
selection[1] as Outlook.MailItem;
byte[] byteArray = mail.RTFBody as byte[];
System.Text.Encoding encoding =
new System.Text.ASCIIEncoding();
string RTF = encoding.GetString(byteArray);
Debug.WriteLine(RTF);
}
}
}
Obtaining Items That Are Displayed in a View
With Outlook 2010, you can obtain items that are displayed in a view by calling the GetTable method on the TableView object. GetTable returns a Table object, but you cannot specify parameters to the method the way that you can with Folder.GetTable. Use GetTable when the view contains a restriction that causes the items in the view to differ from the items in the folder, or when an Instant Search query returns items from multiple folders or stores. The following code example retrieves a Table object from the current view in the Inbox. Note that the Inbox must be the current folder for the TableView.GetTable call to succeed. If you call TableView.GetTable on a folder that is not the current folder in a visible explorer window, Outlook raises an error.
private void DemoViewGetTable()
{
// Obtain the Inbox folder.
Outlook.Folder inbox =
Application.Session.GetDefaultFolder(
Outlook.OlDefaultFolders.olFolderInbox)
as Outlook.Folder;
// Set ActiveExplorer.CurrentFolder to Inbox.
// Inbox must be the current folder
// for View.GetTable to work correctly.
Application.ActiveExplorer().CurrentFolder = inbox;
// Ensure that the current view is TableView.
if (inbox.CurrentView.ViewType ==
Outlook.OlViewType.olTableView)
{
Outlook.TableView view =
inbox.CurrentView as Outlook.TableView;
// No arguments are needed for View.GetTable.
Outlook.Table table = view.GetTable();
Debug.WriteLine("View Count="
+ table.GetRowCount().ToString());
while (!table.EndOfTable)
{
// First row in Table.
Outlook.Row nextRow = table.GetNextRow();
Debug.WriteLine(nextRow["Subject"]
+ " Modified: "
+ nextRow["LastModificationTime"]);
}
}
}
Selection Object Enhancements
The Selection object has been enhanced with a Location property that informs the developer about the UI area for the selection. The Location property is read-only and returns a value in the OlSelectionLocation enumeration. In earlier versions of Outlook, the Selection object contained only items selected in the view list. In Outlook 2010, Selection.Location can return any one of the following OlSelectionLocation values.
Table 4. OlSelectionLocation return values
Value |
Description |
olViewList |
Selected items in the view list. |
olToDoBarTaskList |
Selected items in the To-Do Bar task list. |
olToDoBarAppointmentList |
Selected items in the To-Do Bar appointment list. |
olDailyTaskList |
Selected items in the daily task list in a calendar view. |
olAttachmentWell |
Selected attachments that are in the attachment strip in an Outlook explorer Reading Pane, or the attachment strip in an Outlook inspector. |
When the selection changes, the SelectionChange event occurs for all the selection locations that are listed previously with the exception of olAttachmentWell. When an attachment is selected in the attachment strip, the AttachmentSelectionChange event occurs. Both the SelectionChange and AttachmentSelectionChange events are on the Explorer object.
If you enumerate the Selection object when the SelectionChange event occurs, be sure that your code can handle different item types appropriately. Do not expect that the only object in the Selection object is a MailItem object.
The enhanced Selection object in Outlook 2010 works for any type of view, including the new conversation view in Outlook 2010. The following properties help you to determine whether the current view is a conversation view.
Table 5. TableView properties
TableView Property |
Description |
AlwaysExpandConversation |
Returns or sets a value that indicates whether conversations are always fully expanded in the table view. Read/write. |
ShowConversationByDate |
Returns or sets a Boolean value that indicates whether items in the table view are organized by the conversation date and conversation. Read/write. |
ShowFullConversations |
Returns or sets a Boolean value that indicates whether conversation items from other folders, such as the Sent Items folder, should be displayed as part of the conversation in the table view. Read/write. |
If the value of TableView.ShowConversationByDate is true, the view is using the new conversation arrangement in Outlook 2010. When the current view is a conversation view, a conversation can be a single item conversation, a split conversation, or an expanded conversation. The selection changes depending on whether the selected item is a conversation header or an expanded conversation (multiple items can be selected in the expanded conversation). In addition, Outlook 2010 adds a new ConversationHeader object, and one or more conversation headers can be selected in a view. To determine whether conversation headers are selected in a view, use the new GetSelection method on the Selection object and pass the OlSelectionContents.olConversationHeaders to the method. Calling the GetSelection method returns a Selection object, but in this case the Selection object contains ConversationHeader objects instead of item objects such as MailItem or MeetingItem objects. The DemoConversationHeadersFromSelection function enumerates all the items in selected conversation headers in a conversation view.
private void DemoConversationHeadersFromSelection()
{
// Obtain the Inbox folder.
Outlook.Folder inbox =
Application.Session.GetDefaultFolder(
Outlook.OlDefaultFolders.olFolderInbox)
as Outlook.Folder;
// Set ActiveExplorer.CurrentFolder to the Inbox.
// Inbox must be the current folder.
Application.ActiveExplorer().CurrentFolder = inbox;
//Ensure that current view is a TableView object.
if (inbox.CurrentView.ViewType ==
Outlook.OlViewType.olTableView)
{
Outlook.TableView view =
inbox.CurrentView as Outlook.TableView;
if (view.ShowConversationByDate == true)
{
Outlook.Selection selection =
Application.ActiveExplorer().Selection;
Debug.WriteLine("Selection.Count = " + selection.Count);
// Call GetSelection to create
// a Selection object that contains ConversationHeader objects.
Outlook.Selection convHeaders =
selection.GetSelection(
Outlook.OlSelectionContents.olConversationHeaders)
as Outlook.Selection;
Debug.WriteLine("Selection.Count (ConversationHeaders) = "
+ convHeaders.Count);
if (convHeaders.Count >= 1)
{
foreach (Outlook.ConversationHeader convHeader in
convHeaders)
{
// Enumerate the items in the ConversationHeader.
Outlook.SimpleItems items = convHeader.GetItems();
for (int i = 1; i <= items.Count; i++)
{
// Enumerate only MailItem objects in this example.
if (items[i] is Outlook.MailItem)
{
Outlook.MailItem mail =
items[i] as Outlook.MailItem;
Debug.WriteLine(mail.Subject
+ " Received:" + mail.ReceivedTime);
}
}
}
}
}
}
}
Support for 32-Bit and 64-Bit Platforms
This section discusses some of the issues that affect Outlook extensibility when you use 32-bit or 64-bit Windows with 32-bit or 64-bit Outlook 2010 installed.
Operating System and Office Support for 64-Bit Outlook
Starting in Microsoft Office 2010, Outlook is available as a 32-bit or 64-bit application. On the same computer, the bitness of Outlook depends on the bitness of the Windows operating system (x86 or x64), and of Office, if Office is already installed on that computer. The following are some of the factors that determine the feasibility of installing a 32-bit or a 64-bit version of Outlook:
32-bit Office (and 32-bit Outlook) can be installed on a 32-bit or 64-bit version of the Windows operating system. 64-bit Office (and 64-bit Outlook) can be installed only on a 64-bit operating system.
The default installation of Office on a 64-bit version of the Windows operating system is 32-bit Office.
The bitness of an installed version of Outlook is always the same as the bitness of Office, if Office is installed on the same computer. In other words, a 32-bit version of Outlook cannot be installed on the same computer that already has 64-bit versions of other Office applications installed, such as 64-bit Word or 64-bit Microsoft Excel. Similarly, a 64-bit version of Outlook cannot be installed on the same computer that already has 32-bit versions of other Office applications installed.
Considerations for Existing Applications That Run on 64-bit Outlook
If your 32-bit applications run on a 64-bit version of the Windows operating system with 64-bit Office installed, consider the following issues:
Object model calls should work without modification for 32-bit and 64-bit versions of Outlook.
Native add-ins compiled for 32-bit versions of Outlook (which includes all versions of Outlook previous to Outlook 2010) must be recompiled for 64-bit Outlook.
Outlook add-ins created by using Microsoft Visual Studio Tools for the Microsoft Office system 3.0, and Microsoft Office development tools in Visual Studio 2010, work for both 32-bit and 64-bit versions of Office, provided that they are compiled with the Any CPU option for Target Platform on the Build tab of the Project Properties dialog box.
32-bit standalone applications (.exe) that use the Outlook object model must be recompiled for 64-bit Outlook.
Changes in Messaging API (MAPI) for 64-bit Outlook
Although there is no difference between the Outlook 2010 object models for the 32-bit and 64-bit versions of Outlook, there are some small changes in MAPI functions that resulted from the introduction of 64-bit MAPI with 64-bit Outlook. Typically, those changes are related to 64-bit types, such as ULONG_PTR, that must be passed to MAPI functions.
MAPI applications include standalone applications such as Microsoft Communicator and MFCMAPI, and service providers such as address book, store, and transport providers. For MAPI method and function calls to work in a MAPI application (with the exception of one Simple MAPI function, MAPISendMail), the bitness of the MAPI application must be the same as the bitness of the MAPI subsystem on the computer that the application is targeted to run on. The bitness of the MAPI subsystem, in turn, is determined by and always the same as the bitness of the installed version of Outlook.
If your solution is native and calls MAPI directly, see the Welcome to the Outlook 2010 MAPI Reference for guidance on how to port your solution to work with 64-bit Outlook. The following topics will be of particular interest to MAPI developers:
Exchange Client Extensions Deprecated
Exchange Client Extensions (ECEs) have been deprecated in Outlook 2010. ECEs were introduced with the Microsoft Exchange client in 1995, when the client was a 16-bit mail application running against the earliest versions of Exchange Server. ECEs must be written in native code, typically by using C++ and relying heavily on MAPI. When Outlook replaced the Exchange client, ECEs were used to extend Outlook 97-98 until COM add-ins replaced ECEs in Outlook 2000.
ECEs will continue to operate as expected in Outlook 2007 and earlier. However, ECEs will not load in 32-bit and 64-bit versions of Outlook 2010. To redesign your existing ECE solution, consider the following options:
Rewrite your ECE as a COM add-in by using native or managed code. Unlike ECEs, an add-in represents a strategic extensibility technology that is fully supported in Outlook 2010. By using an Outlook add-in, you can build Outlook form regions and extend the Office Fluent UI. For more information, see Building an Outlook 2007 Form Region with a Managed Add-In, the Office Fluent User Interface Developer Center, and the Outlook Developer Center on MSDN.
Rewrite your ECE as a Windows service application by using native code and MAPI. If you write a Windows service application, you must use MAPI to access Outlook items instead of the Outlook object model.
CDO 1.21 Not Supported for Outlook 2010
Collaboration Data Objects (CDO) 1.2.1 is a client library that provides a thin wrapper over Extended MAPI functionality. CDO does not install with Outlook 2010, and is available only as a download. For more information, see Collaboration Data Objects, version 1.2.1 in the Microsoft Download Center. CDO is not guaranteed to work correctly in an environment with multiple Exchange accounts. CDO 1.2.1 is a 32-bit client library and will not operate with 64-bit Outlook 2010. Therefore, CDO 1.2.1 is not supported for use with Outlook 2010. Most of the CDO 1.2.1 functionality has been incorporated into the Outlook 2010 object model. As an alternative to CDO 1.2.1, update existing solutions that depend on CDO to use the Outlook object model or to use MAPI directly.
Multiple Exchange Accounts
One of the exciting new features in Outlook 2010 is the ability to run multiple Exchange accounts. The Outlook object model was originally designed around one and only one Exchange account in a given profile. In Outlook 2007, properties and methods associated with an Exchange account are available on the Namespace object. To accommodate multiple Exchange accounts, the Account object now exposes several new properties and methods. Properties such as ExchangeMailboxServerName, ExchangeMailboxServerVersion, and ExchangeConnectionMode are still on the Namespace object, but return values that apply to the primary Exchange account only. For Exchange properties that relate to all Exchange accounts in a profile, use the Account object. The following table shows the properties and methods that have been added to the Account object in Outlook 2010 to support multiple Exchange accounts.
Table 6. Account object member additions
Name |
Type |
Description |
AutoDiscoverConnectionMode |
Property |
Returns an OlAutoDiscoverConnectionMode constant that specifies the type of connection to use for the automatic-discovery service of the Exchange server that hosts the account mailbox. Read-only. |
AutoDiscoverXml |
Property |
Returns a string that represents information in XML that is retrieved from the automatic-discovery service of the Exchange server that is associated with the account. Read-only. |
CurrentUser |
Property |
Returns a Recipient object that represents the current user identity for the account. Read-only. |
DeliveryStore |
Property |
Returns a Store object that represents the default delivery store for the account. Returns Null (Nothing in Visual Basic) if the account does not have a default delivery store. |
ExchangeConnectionMode |
Property |
Returns an OlExchangeConnectionMode constant that indicates the current connection mode for the Exchange Server that hosts the account mailbox. Read-only. |
ExchangeMailboxServerName |
Property |
Returns a string that represents the name of the Exchange Server that hosts the account mailbox. |
ExchangeMailboxServerVersion |
Property |
Returns the full version number of the Exchange Server that hosts the account mailbox. Read-only. |
GetAddressEntryFromID |
Method |
Returns an AddressEntry object that represents the address entry specified by ID. |
GetRecipientFromID |
Method |
Returns a Recipient object identified by the specified entry ID. |
Enumerating Accounts in a Profile
The following EnumerateAccounts function provides a straightforward example of how to use the new properties on the Account object.
private void EnumerateAccounts()
{
Outlook.Accounts accounts =
Application.Session.Accounts;
foreach (Outlook.Account account in accounts)
{
try
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("Account: " + account.DisplayName);
if (string.IsNullOrEmpty(account.SmtpAddress)
|| string.IsNullOrEmpty(account.UserName))
{
Outlook.AddressEntry oAE =
account.CurrentUser.AddressEntry
as Outlook.AddressEntry;
if (oAE.Type == "EX")
{
Outlook.ExchangeUser oEU =
oAE.GetExchangeUser()
as Outlook.ExchangeUser;
sb.AppendLine("UserName: " +
oEU.Name);
sb.AppendLine("SMTP: " +
oEU.PrimarySmtpAddress);
sb.AppendLine("Exchange Server: " +
account.ExchangeMailboxServerName);
sb.AppendLine("Exchange Server Version: " +
account.ExchangeMailboxServerVersion);
}
else
{
sb.AppendLine("UserName: " +
oAE.Name);
sb.AppendLine("SMTP: " +
oAE.Address);
}
}
else
{
sb.AppendLine("UserName: " +
account.UserName);
sb.AppendLine("SMTP: " +
account.SmtpAddress);
if (account.AccountType ==
Outlook.OlAccountType.olExchange)
{
sb.AppendLine("Exchange Server: " +
account.ExchangeMailboxServerName);
sb.AppendLine("Exchange Server Version: " +
account.ExchangeMailboxServerVersion);
}
}
If (account.DeliveryStore !=null)
{
sb.AppendLine("Delivery Store: " +
account.DeliveryStore.DisplayName);
}
sb.AppendLine("---------------------------------");
Debug.Write(sb.ToString());
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
}
Creating a Sendable Item and Setting the From Field
One of the most commonly used methods in the Outlook object model is the CreateItem method on the Application object. This method has no information about multiple accounts, and always creates the item for the primary account in the profile. If you want to create a sendable item that "understands" the account associated with a given folder, you must determine the Store of the current folder and then enumerate the Accounts collection to determine whether the DeliveryStore for a given account matches the Store property of the current folder. After you determine the correct Account for the Store property of the current folder, you can set the Sender property of a MailItem or the SendUsingAccount property of an AppointmentItem. The CreateMailItemUsingAccount and CreateMeetingRequestUsingAccount functions show you how to handle this functionality in a multiple-account scenario.
private void CreateMailItemFromAccount()
{
Outlook.AddressEntry addrEntry = null;
Outlook.Folder folder =
Application.ActiveExplorer().CurrentFolder
as Outlook.Folder;
Outlook.Store store = folder.Store;
Outlook.Accounts accounts =
Application.Session.Accounts;
foreach (Outlook.Account account in accounts)
{
if (account.DeliveryStore == store)
{
addrEntry =
account.CurrentUser.AddressEntry;
break;
}
}
Outlook.MailItem mail =
Application.CreateItem(
Outlook.OlItemType.olMailItem)
as Outlook.MailItem;
if (addrEntry != null)
{
mail.Sender = addrEntry;
}
mail.Display(false);
}
private void CreateMeetingRequestFromAccount()
{
Outlook.Account acct = null;
Outlook.Folder folder =
Application.ActiveExplorer().CurrentFolder
as Outlook.Folder;
Outlook.Store store = folder.Store;
Outlook.Accounts accounts =
Application.Session.Accounts;
foreach (Outlook.Account account in accounts)
{
if (account.DeliveryStore == store)
{
acct = account;
break;
}
}
Outlook.AppointmentItem appt =
Application.CreateItem(
Outlook.OlItemType.olAppointmentItem)
as Outlook.AppointmentItem;
appt.MeetingStatus =
Outlook.OlMeetingStatus.olMeeting;
if (acct != null)
{
appt.SendUsingAccount=acct;
}
appt.Display(false);
}
Determine the Global Address List for an Exchange Store
When multiple Exchange accounts are available in a profile, you can enumerate the AddressLists available for a given Store object or determine the Global Address List (GAL) for a given Exchange store. The following code example returns an AddressList object that represents the GAL for the Store object passed to the GetGlobalAddressList function. You can use the GetGlobalAddressList function in the code example to return an AddressList object that represents the GAL for the Store of the ActiveExplorer().CurrentFolder. The example code gives you a way to duplicate the native behavior of Outlook in an environment with multiple Exchange accounts. When the user clicks New Mail, the first address list displayed in the Outlook address book dialog box is the Global Address List for the Store of the current folder if the store is the delivery store for an Exchange account. Use the DisplayGlobalAddressListForStore and GetGlobalAddressList functions to duplicate the behavior programmatically.
void DisplayGlobalAddressListForStore()
{
Outlook.Folder currentFolder =
Application.ActiveExplorer().CurrentFolder
as Outlook.Folder;
Outlook.Store currentStore = currentFolder.Store;
if (currentStore.ExchangeStoreType !=
Outlook.OlExchangeStoreType.olNotExchange)
{
Outlook.SelectNamesDialog snd =
Application.Session.GetSelectNamesDialog();
Outlook.AddressList addrList =
GetGlobalAddressList(currentStore);
if (addrList != null)
{
snd.InitialAddressList = addrList;
snd.Display();
}
}
}
public Outlook.AddressList GetGlobalAddressList(Outlook.Store store)
{
string PR_EMSMDB_SECTION_UID =
@"https://schemas.microsoft.com/mapi/proptag/0x3D150102";
if (store == null)
{
throw new ArgumentNullException();
}
Outlook.PropertyAccessor oPAStore = store.PropertyAccessor;
string storeUID = oPAStore.BinaryToString(
oPAStore.GetProperty(PR_EMSMDB_SECTION_UID));
foreach (Outlook.AddressList addrList
in Application.Session.AddressLists)
{
Outlook.PropertyAccessor oPAAddrList =
addrList.PropertyAccessor;
string addrListUID = oPAAddrList.BinaryToString(
oPAAddrList.GetProperty(PR_EMSMDB_SECTION_UID));
// Return addrList if match on storeUID
// and type is olExchangeGlobalAddressList.
if (addrListUID == storeUID && addrList.AddressListType ==
Outlook.OlAddressListType.olExchangeGlobalAddressList)
{
return addrList;
}
}
return null;
}
Enumerating AddressList Objects for a Delivery Store
The next code example uses a property in Outlook 2010 that serves as a unique identifier for both Store and AddressList objects. This property, named PR_EMSMDB_SECTION_UID in the following code example, is used to evaluate whether a given AddressList belongs to a specific Store object. The Store object must represent a delivery store. In the case of multiple Exchange accounts, one Store object acts as the delivery store for a given Exchange account. The EnumerateAddressListsForStore function enumerates the address lists for ActiveExplorer().CurrentFolder.Store.
private void EnumerateAddressListsForStore()
{
Outlook.Folder currentFolder =
Application.ActiveExplorer().CurrentFolder
as Outlook.Folder;
Outlook.Store currentStore = currentFolder.Store;
List<Outlook.AddressList> addrListsForStore =
GetAddressLists(currentStore);
foreach (Outlook.AddressList addrList in addrListsForStore)
{
Debug.WriteLine(addrList.Name
+ " " + addrList.AddressListType.ToString()
+ " Resolution Order: " +
addrList.ResolutionOrder);
}
}
public List<Outlook.AddressList> GetAddressLists(Outlook.Store store)
{
List<Outlook.AddressList> addrLists =
new List<Microsoft.Office.Interop.Outlook.AddressList>();
string PR_EMSMDB_SECTION_UID =
@"https://schemas.microsoft.com/mapi/proptag/0x3D150102";
if (store == null)
{
throw new ArgumentNullException();
}
Outlook.PropertyAccessor oPAStore = store.PropertyAccessor;
string storeUID = oPAStore.BinaryToString(
oPAStore.GetProperty(PR_EMSMDB_SECTION_UID));
foreach (Outlook.AddressList addrList
in Application.Session.AddressLists)
{
Outlook.PropertyAccessor oPAAddrList =
addrList.PropertyAccessor;
string addrListUID = oPAAddrList.BinaryToString(
oPAAddrList.GetProperty(PR_EMSMDB_SECTION_UID));
// Return addrList if match on storeUID
// and type is olExchangeGlobalAddressList.
if (addrListUID == storeUID)
{
addrLists.Add(addrList);
}
}
return addrLists;
}
Conclusion
Outlook 2010 has exciting challenges in store for you as a developer. You can improve the UI of your solution to integrate seamlessly with the new UI of Outlook 2010, and if your solution uses custom folders to store information, you can use the new Solutions module and custom folder icons to enhance discoverability. Your code should be able to accommodate multiple Exchange accounts and to take advantage of the changes to the object model that grew out of the powerful new features in Outlook 2010, such as conversation views and item selection. Finally, changes to the platform, such as the support for 32-bit and 64-bit versions of Outlook, introduce further change and complexity, especially for advanced applications written at the MAPI level.
Additional Resources
MAPI
Welcome to the Outlook 2010 MAPI Reference
Outlook 2010: MAPI Header Files
Outlook 2010 Messaging API (MAPI) Samples
Office Fluent UI Extensibility
Customizing Context Menus in Office 2010
Introduction to the Office 2010 Backstage View for Developers
Ribbon Extensibility in Office 2010: Tab Activation and Auto-Scaling
Office 2010 Reference: Office Fluent User Interface XML Schema
Office 2010 Help Files: Office Fluent User Interface Control Identifiers