Mirror

Outlook from Delphi (Views: 302)


Problem/Question/Abstract:

Outlook from Delphi

Answer:

Automating Microsoft Outlook

Microsoft Office 97 appears to be five well-integrated applications. It is, in fact, much more. Office 97 was created using Microsoft's Component Object Model (COM). The Office applications are composed of a series of COM servers you can access from your Delphi applications using Automation (formerly know as OLE Automation). Beginning with Outlook 98, this article series will explore the object model of each of the office applications - and how you can use them from Delphi.

The Outlook object model consists of objects and collections of objects (see Figure 1). The top-level object in Outlook 98 is the Application object. The Application object is the root of the object tree and provides access to all the other Outlook objects. The Application object is unique in that it's the only object you can gain access to by calling CreateOleObject from a Delphi (or any other) application. Next comes the NameSpace object, which provides access to a data source. The only available data source in Outlook 98 is the MAPI message store.


Figure 1: The Outlook object model.

The MAPIFolders collection is just that - a collection of MAPI folders. You can think of collections as arrays of objects, somewhat like a Delphi TList. However, collection objects can be referenced by name or number. The MAPIFolder object in Figure 1 represents one of the folders in the MAPIFolders collection. Each MAPIFolder contains a Folders collection, and each of these contains an Items collection that contains the items appropriate to that folder. For example, the Contacts folder contains contact items.

Figure 2 shows the main form of a Delphi project that displays the MAPIFolders collection, the Folders collection of the MAPI Personal folder, and the Items in the Contacts folder. Listing One  displays the code from the Open Outlook button's OnClick event handler.


Figure 2: The MAPI Folders collection displayed in a Delphi form.

The code in Listing One begins by declaring four Variant variables for use as references to various Outlook objects. The call to CreateOleObject loads the Outlook server and returns a reference to the Application object. The parameter passed to CreateOleObject, Outlook.Application, is the class name Outlook registers itself as when it's installed. Using the Application object you can get a reference to any other Outlook object.

Calling the Application object's GetNameSpace method returns a reference to the NameSpace passed as a parameter. Using the MAPI NameSpace reference variable, Mapi, the code loops through the MAPIFolders collection and adds the name of each folder to the MapiList listbox. As with all objects in object-oriented programming, Outlook objects have properties, methods, and events. The Count property of the Folders collection is used to limit the number of times the for loop executes. All collections have a Count property to provide the number of objects in the collection. Each Folder in the MAPIFolders collection also has a Name property.

As you can see in Figure 2, the MAPIFolders collection contains two folders, Microsoft Mail Shared Folders and Personal Folders. The following statement gets a reference to the Personal Folders collection from the MAPIFolders collection. While the for loop that displayed the names of the MAPI Folders accessed the MAPIFolders collection by number, the statement:

Personal := Mapi.Folders('Personal Folders');

indexes the collection by name. The next for loop uses the reference to the Personal Folder to display the names of all the folders in its Folders collection in the second listbox in Figure 2. The code then gets a reference to the Contacts folder and uses it to loop through the Contacts folder's Items collection. One of the properties of a Contact item is FullName; this property is added to the third listbox to display the names of the contacts.

Clearly, the secret to working with Outlook 98 from your Delphi applications is understanding the Outlook object hierarchy and the properties, methods, and events of each object. Outlook 97 includes a Help file, VBAOUTL.HLP, that contains this information; however, I have been unable to find it on the Outlook 98 CD. Fortunately, very little has changed in Outlook 98. (Outlook 2000 is a different story, and will be the topic of a future article.)

Working with Contacts

Listing Two  shows the OnClick event handler from the LoadTbl project that accompanies this article. This code demonstrates how to search the Outlook Contacts folder for the records you wish to select and copy them to a database table.

As in the example shown in Listing One, this one begins by getting the Application object and the MAPI NameSpace object. Next, a reference is obtained using the statement:

ContactItems := Mapi.Folders('Personal Folders').
Folders('Contacts').Items;

This statement demonstrates how you can chain objects together using dot notation to get a reference to a low-level object without having to get individual references to each of the higher level objects. In this case, five levels of intervening objects are specified to get to the Items object of the Contacts folder. These objects are:

The MAPI NameSpace object
The Folders collection
The Personal Folders object
The Folders collection
The Contacts object

You can use this notation to get a reference to any Outlook object in a single statement. The next new feature of this method is the call to the Find method of the ContactItems collection. Almost all collection objects have a Find method you can use to locate a particular item in the collection using one or more of its properties. In this example, the statement:

CurrentContact := ContactItems.Find(' [CompanyName] = ' +
  QuotedStr('Borland International'));

finds the first contact item where the value of the CompanyName property is equal to Borland International. If no matching item is found, the Variant CurrentContact will be empty. The while loop inserts a new record into the database table, and assigns each of the Contact item's properties to the corresponding field in the table. The while loop continues until CurrentContact is empty, indicating that no more items matching the search criteria can be found. At the end of the while loop, the call to FindNext finds the next matching record, if there is one. If no record is found, CurrentContact is set to empty and the loop terminates.

Creating new Contact folders and records is just as easy. Suppose you want to copy all your Contact records for Borland employees into a new folder. The code in Listing Three  from the NewFolder sample project will do the job.

This method begins by getting the Application, MAPI NameSpace, and Contacts folder's Items object. Next, it uses a for loop to scan the Folders collection looking for the Borland Contacts folder. If the folder is found, its number is assigned to the ToRemove variable. The Borland Contacts folder is deleted by calling the Folders collection's Remove method and passing the ToRemove variable as the parameter.

Next, a call to the Folders collection's Add method creates the Borland Contacts folder. Add takes two parameters. The first is the name of the folder to be created. The second parameter is the folder type and can be olFolderCalendar, olFolderContacts, olFolderInbox, olFolderJournal, olFolderNotes, or olFolderTasks. To find the values of these and any other constants you need, search the VBAOUTL.HLP file for Microsoft Outlook Constants. The next statement gets a reference to the new Borland Contacts folder and stores it in the BorlandContacts variable.

A call to the Contacts folder's Items collection's Find method locates the first record for a Borland employee. The while loop is used to iterate through all the Borland employees in the Contacts folder. At the top of the loop a new record is added to the Borland Contacts folder by calling the folder's Items collection's Add method.

Add takes no parameters; it simply inserts a new empty record and returns a reference to the new record, which is saved in the NewContact variable. The statements that follow assign values from the existing record to the new one. Finally, the new record's Save method is called. This is a critical step. If you don't call Save, no errors will be generated - but there will be no new records in the folder. When the while loop terminates Outlook is closed by assigning the constant Unassigned to the OutlookApp variable.

Other Outlook Objects

The Folders collection of the Personal Folder object contains the following folders:

Deleted Items
Inbox
Outbox
Sent Items
Calendar
Contacts
Journal
Notes
Tasks
Drafts

You can work with the Items collection of any of these folders using the same code shown for working with Contacts. Only the properties of the items are different. Listing Four  shows a method that copies to a Paradox table all appointments that are all-day events and whose start date is greater than 4/27/99. This example copies the Start, End, Subject and BusyStatus properties to the table. Note that this example uses a more sophisticated find expression than previous examples. Find supports the >, <, >=, <=, = and <> operators, as well as the logical operators and, or, and not, which allows you to construct complex search expressions.

Conclusion

Delphi applications can easily act as Automation clients, allowing your applications to interact with the Microsoft Office Suite applications in any way you wish. Using Outlook you can extract contact information to update a central database, add new contacts derived from other sources, create new folders, and add items of any type. One of Outlook's limitations is its lack of a powerful reporting tool. With a Delphi application you can provide much more powerful reporting capabilities for Outlook data. With a basic understanding of the Outlook object model and a copy of the VBAOUTL.HLP help file you are well on your way.


Begin Listing One - Displaying Outlook objects
procedure TForm1.OpenBtnClick(Sender: TObject);
var
  OutlookApp,
    Mapi,
    Contacts,
    Personal: Variant;
  I: Integer;
begin
  { Get the Outlook Application object. }
  OutlookApp := CreateOleObject('Outlook.Application');
  { Get the MAPI NameSpace object. }
  Mapi := OutlookApp.GetNameSpace('MAPI');
  { Loop through the MAPI Folders collection and add the
   Name of each folder to the listbox. }
  for I := 1 to Mapi.Folders.Count do
    MapiList.Items.Add(Mapi.Folders(I).Name);
  { Get the Personal folder from the MAPI folders
   collection. }
  Personal := Mapi.Folders('Personal Folders');
  { Loop through the Personal Folders Collection and add
   the name of each folder to the listbox. }
  for I := 1 to Personal.Folders.Count do
    PersonalList.Items.Add(Personal.Folders(I).Name);
  { Get the Contacts folder from the Personal Folders
   collection. }
  Contacts := Personal.Folders('Contacts');
  { Loop through the Contacts folder's Items collection
   and add the FullName property of each Item
   to the listbox. }
  for I := 1 to Contacts.Items.Count do
    ContactsList.Items.Add(Contacts.Items(I).FullName);
  { Close Outlook. }
  OutlookApp := Unassigned;
end;
End Listing One

Begin Listing Two - Searching for contacts
procedure TLoadTableForm.LoadBtnClick(Sender: TObject);
var
  OutlookApp,
    Mapi,
    ContactItems,
    CurrentContact: Variant;
begin
  { Get the Outlook Application object. }
  OutlookApp := CreateOleObject('Outlook.Application');
  { Get the MAPI NameSpace object. }
  Mapi := OutlookApp.GetNameSpace('MAPI');
  { Get the Items collection from the Contacts folder. If
   you don't do this, FindNext will not work. }
  ContactItems := Mapi.Folders('Personal Folders').
    Folders('Contacts').Items;
  { Load Contacts into table. }
  with ContactTable do
  begin
    EmptyTable;
    Open;
    DisableControls;
    CurrentContact :=
      ContactItems.Find('[CompanyName] = ' +
      QuotedStr('Borland International'));
    while not VarIsEmpty(CurrentContact) do
    begin
      Insert;
      FieldByName('EntryId').AsString :=
        CurrentContact.EntryId;
      FieldByName('LastName').AsString :=
        CurrentContact.LastName;
      FieldByName('FirstName').AsString :=
        CurrentContact.FirstName;
      FieldByName('CompanyName').AsString :=
        CurrentContact.CompanyName;
      FieldByName('BusAddrStreet').AsString :=
        CurrentContact.BusinessAddressStreet;
      FieldByName('BusAddrPOBox').AsString :=
        CurrentContact.BusinessAddressPostOfficeBox;
      FieldByName('BusAddrCity').AsString :=
        CurrentContact.BusinessAddressCity;
      FieldByName('BusAddrState').AsString :=
        CurrentContact.BusinessAddressState;
      FieldByName('BusAddrPostalCode').AsString :=
        CurrentContact.BusinessAddressPostalCode;
      FieldByName('BusinessPhone').AsString :=
        CurrentContact.BusinessTelephoneNumber;
      Post;
      CurrentContact := ContactItems.FindNext;
    end; // while
    EnableControls;
  end; // with
  { Close Outlook. }
  OutlookApp := Unassigned;
end;
End Listing Two

Begin Listing Three - Creating a Contacts folder and new contacts
procedure TCreateFolderFrom.CreateBtnClick(Sender: TObject);
const
  olFolderContacts = 10;
  olContactItem = 2;
var
  OutlookApp,
    Mapi,
    NewContact,
    BorlandContacts,
    ContactItems,
    CurrentContact: Variant;
  I,
    ToRemove: Integer;
begin
  { Get the Outlook Application object. }
  OutlookApp := CreateOleObject('Outlook.Application');
  { Get the MAPI NameSpace object. }
  Mapi := OutlookApp.GetNameSpace('MAPI');
  { Get the Items collection from the Contacts folder. If
   you don't do this,FindNext will not work. }
  ContactItems := Mapi.Folders('Personal Folders').
    Folders('Contacts').Items;
  { Remove the test folder. }
  ToRemove := 0;
  for I := 1 to Mapi.Folders('Personal Folders').
    Folders.Count do
    if Mapi.Folders('Personal Folders').Folders(I).Name =
      'Borland Contacts' then
    begin
      ToRemove := I;
      Break;
    end; // if
  if ToRemove <> 0 then
    Mapi.Folders('Personal Folders').
      Folders.Remove(ToRemove);
  { Create a new folder. }
  Mapi.Folders('Personal Folders').
    Folders.Add('Borland Contacts', olFolderContacts);
  BorlandContacts := Mapi.Folders('Personal Folders').
    Folders('Borland Contacts');
  { Load Contacts into new folder. }
  CurrentContact := ContactItems.Find('[CompanyName] = ' +
    QuotedStr('Borland International'));
  while not VarIsEmpty(CurrentContact) do
  begin
    { Add a new item to the folder. }
    NewContact := BorlandContacts.Items.Add;
    { Assign values to the fields in the item record. }
    NewContact.FullName := 'John Doe';
    NewContact.LastName := CurrentContact.LastName;
    NewContact.FirstName := CurrentContact.FirstName;
    NewContact.CompanyName := CurrentContact.CompanyName;
    NewContact.BusinessAddressStreet :=
      CurrentContact.BusinessAddressStreet;
    NewContact.BusinessAddressPostOfficeBox :=
      CurrentContact.BusinessAddressPostOfficeBox;
    NewContact.BusinessAddressCity :=
      CurrentContact.BusinessAddressCity;
    NewContact.BusinessAddressState :=
      CurrentContact.BusinessAddressState;
    NewContact.BusinessAddressPostalCode :=
      CurrentContact.BusinessAddressPostalCode;
    NewContact.BusinessTelephoneNumber :=
      CurrentContact.BusinessTelephoneNumber;
    { Save the new record. }
    NewContact.Save;
    { Find the next record in the Contacts folder. }
    CurrentContact := ContactItems.FindNext;
  end; // while
  { Close Outlook. }
  OutlookApp := Unassigned;
end;
End Listing Three

Begin Listing Four - Reading Calendar folder
procedure TLoadTableForm.LoadBtnClick(Sender: TObject);
var
  OutlookApp,
    Mapi,
    ApptItems,
    CurrentAppt: Variant;
begin
  { Get the Outlook Application object. }
  OutlookApp := CreateOleObject('Outlook.Application');
  { Get the MAPI NameSpace object. }
  Mapi := OutlookApp.GetNameSpace('MAPI');
  { Get the Items collection from the Contacts folder. If
   you don't do this, FindNext will not work. }
  ApptItems := Mapi.Folders('Personal Folders').
    Folders('Calendar').Items;
  { Load Contacts into table. }
  with ApptTable do
  begin
    EmptyTable;
    Open;
    DisableControls;
    CurrentAppt := ApptItems.Find('[Start] > ' +
      '"4/27/99" and [AllDayEvent] = True');
    while not VarIsEmpty(CurrentAppt) do
    begin
      Insert;
      FieldByName('Start').AsDateTime := CurrentAppt.Start;
      FieldByName('Subject').AsString :=
        CurrentAppt.Subject;
      FieldByName('End').AsDateTime := CurrentAppt.End;
      FieldByName('Busy').AsBoolean :=
        CurrentAppt.BusyStatus;
      Post;
      CurrentAppt := ApptItems.FindNext;
    end; // while
    EnableControls;
  end; // with
  { Close Outlook. }
  OutlookApp := Unassigned;
end;
End Listing Four


Component Download: outlook_from_delphi.zip

<< Back to main page