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 |