View RSS Feed

Development Team Blog

An Outlook Addresses Lookup List

Rate this Entry
This week I had the need for a selection list with e-mail addresses that are stored in my Microsoft Outlook address book(s). So I created one. This blog explains in steps what you need to do to make one as well. The blog is based on version 16.1 of Visual DataFlex. If you do not have that version you cannot create the component.

The Selection List
One of the enhancements of Visual DataFlex 16.1 is a cCJGridPromptList class which offers a selection list that is not getting its data from a Data Dictionary object but from a datasource that you fill yourself. The Microsoft Outlook addresses are data but they cannot be reached via a Data Dictionary Object.
Create a new component in your workspace and use a ModalPanel class for that. Name the dialog oOutlookAddressesLookup and drop a cCJGridPromptList class on the dialog. In the cCJGridPromptList object you should create two columns; one for the display name of the e-mail address and one for the e-mail address itself. Optional you can add a cCJGridColumnRowIndicator object too, for this blog I choose to do so.
Code:
Use Windows.pkg
Use DFClient.pkg
Use cCJGridPromptList.pkg
Use cCJGridColumnRowIndicator.pkg
Use cCJGridColumn.pkg
Use Outlook.pkg

Object oOutlookAddressesLookup is a ModalPanel
    Set Size to 133 292
    Set Location to 4 5
    Set Border_Style to Border_Thick
    Set Label to "Select an Outlook contact"

    Object oSelList is a cCJGridPromptList
        Set peAnchors to anAll
        Set Size to 105 280
        Set Location to 5 5

        Object oCJGridColumnRowIndicator is a cCJGridColumnRowIndicator
            Set piWidth to 18
        End_Object

        Object oNameColumn is a cCJGridColumn
            Set piWidth to 200
            Set psCaption to "Name"
        End_Object
        
        Object oAddressColumn is a cCJGridColumn
            Set piWidth to 200
            Set psCaption to "E-mail address"
        End_Object
    End_Object

    Object oSelectButton is a Button
        Set Label to "&Select"
        Set Location  to 115 128
        Set peAnchors to anBottomRight

        Procedure OnClick
            Send Ok of oSelList
        End_Procedure
    End_Object

    Object oCancelButton is a Button
        Set Label to "&Cancel"
        Set Location  to 115 182
        Set peAnchors to anBottomRight

        Procedure OnClick
            Send Cancel of oSelList
        End_Procedure
    End_Object

    Object oSearchButton is a Button
        Set Label to "Search..."
        Set Location  to 115 236
        Set peAnchors to anBottomRight

        Procedure OnClick
            Send Search of oSelList
        End_Procedure
    End_Object

    On_Key Key_Alt+Key_S Send KeyAction of oSelectButton
    On_Key Key_Alt+Key_C Send KeyAction of oCancelButton
End_Object
By default the selection list lets you select one record but for this example I want to be able to select more than one and thus I set the property pbMultipleSelection to true.

Outlook
Another key player in this blog is Microsoft Outlook. Without having Microsoft Outlook on your machine you cannot get this blog to work. The code you will see will work with Microsoft Outlook 2003, 2007 and 2010 BUT on version 2003 you will get the "someone wants to access..." question. Up from revision 2007 and an installed and alive approved virus scanner the message will no longer appear. So my advise it to use Microsoft Outlook 2007 as minimal version. You can test if Microsoft Outlook is installed with looking in your registry. For this we wrote a function named CheckIfComRegistered. The code for this function is:
Code:
External_Function CLSIDFromString "CLSIDFromString" ole32.dll Pointer lpsz Pointer pclsid Returns Handle
External_Function StringFromCLSID "StringFromCLSID" ole32.dll Pointer rclsid Pointer ppsz Returns Integer

Function CheckIfComRegistered String sName Returns String
    String sBuffer sClass sAddress
    Integer iResult
    Integer iAddress
    
    Move 0 to iAddress
    Move (Repeat(Character(0),255)) to sBuffer

    Move (StrToWStr(sName)) to sName
    Move (CLSIDFromString(AddressOf(sName),AddressOf(sBuffer))) to iResult
    Move (Repeat(Character(0),255)) to sClass
    Move (StringFromCLSID(AddressOf(sBuffer),AddressOf(iAddress))) to iResult
    Move (CopyMemory(AddressOf(sClass),iAddress,76)) to iResult
    Move (WStrToStr(sClass)) to sClass
    Function_Return sClass
End_Function
You use above function to test if Outlook is installed via:
Code:
Get CheckIfComRegistered "Outlook.Application" to sClass
Move (sClass = "{0006F03A-0000-0000-C000-000000000046}") to bInstalled
The next step around the use of Microsoft Outlook is generating the COM classes. This is easy and can be done from the Visual DataFlex Studio. Go to Create New and the tab-page classes. In there select "Import COM Automation" and click the browse button. Now look for MSOUTL.OLB (at my computer this was in C:\Program Files\Microsoft Office\Office14\MSOUTL.OLB). As you can see from the version I am using Outlook 2010 (version 14). Let the Visual DataFlex Studio generate the class.

Name Clashes
The class code generator attempt to avoid name clashes by prefixing classnames with cCom and methods with Com. While this is a good attempt it is not good enough if you want to combine multiple parts of Microsoft Office in one environment. Therefor I suggest you make a change in the package that is generated for you. Replace all cCom class names into cOutlook class names. Do this in two steps. Using the search and replace feature in the Visual DataFlex Studio for this. First replace all "Class cCom" with "Class cOutlook" and then the class import instructions via "tocol cCom" with "tocol cOutlook". Using the two steps to avoid that cCom at the wrong places are being replaced.

Addresses, How to Get Them?
How to get to the addresses stored in Outlook? You need to study at the Microsoft Outlook Object Model documentation in MSDN. It learned me that address information (such as e-mail address and name) is stored in an AddressEntry object. An AddressEntry object belongs to a collection of AddressEntries. The AddressEntries collection can be reached via an AddressList object. Note that the object model seems to suggest that AddressEntries belong to AddressLists. An AddressList belongs to a collection of AddressLists. You can get access to the AddressLists via the property AddressLists in a NameSpace object and access to the NameSpace object is via the Application object.
So we need to create the following objects in our code:
Code:
Object oOutlookApplication is a cOutlookApplication
End_Object

Object oNameSpace is a cOutlookNameSpace
End_Object

Object oAddressLists is a cOutlookAddressLists
End_Object

Object oAddressList is a cOutlookAddressList
End_Object

Object oAddressEntries is a cOutlookAddressEntries
End_Object

Object oAddressEntry is a cOutlookAddressEntry
End_Object
To read the addresses you need to make a connection between your Visual DataFlex application and Outlook by sending the message CreateComObject to the oOutlookApplication object. Do this in either the Activating event of the cCJGridPromptList object or in a method started from this event.
Code:
Procedure Activating
    Forward Send Activating
    
    Send LoadAddresses
End_Procedure

Procedure Procedure LoadAddresses
    Boolean bIsCreated

    Get IsComObjectCreated of oOutlookApplication to bIsCreated
    If (not (bIsCreated)) Begin
        Send CreateComObject of oOutlookApplication
    End
End_Procedure
The other COM objects are connected by retrieving dispatch IDs and assigning them to the Visual DataFlex objects. First we need to get a handle (Dispatch ID) to the NameSpace which we get by adding:
Code:
Get ComGetNamespace of oOutlookApplication "MAPI" to vNameSpace
Set pvComObject of oNameSpace to vNameSpace
The only available NameSpace is MAPI. After the dispatch ID is assigned to the oNameSpace object you can talk (send messages) to the oNameSpace object. Messages like Login and the message to retieve the handle to the address lists object.
Code:
Get psProfileName to sProfileName
Get psPassword to sPassword
Send ComLogon of oNameSpace sProfileName sPassword True False

Get ComAddressLists of oNameSpace to vAddressLists
Set pvComObject of oAddressLists to vAddressLists
The oAddressLists object is, as mentioned before, a collection of individual address list objects. Therefore we enumerate the addresslist objects via the messages Count and Item.
Code:
Get ComCount of oAddressLists to iAddressLists
For iAddressList from 1 to iAddressLists
    Get ComItem of oAddressLists iAddressList to vAddressList
    Set pvComObject of oAddressList to vAddressList
Each address list has a couple of properties but for this blog is only the AddressEntries property interesting. This property delivers the dispatch ID for the collection of AddressEntry objects, an AddressEntries object.
Code:
Get ComAddressEntries of oAddressList to vAddressEntries
Set pvComObject of oAddressEntries to vAddressEntries
Get ComCount of oAddressEntries to iAddressEntries
For iAddressEntry from 1 to iAddressEntries
    Get ComItem of oAddressEntries iAddressEntry to vAddressEntry
Important to know is that each address entry in the collection of address entries can have its own type. The documentation in MSDN is not clear about what the values exactly are but I found that I am interested in the SMTP entries only.
Code:
Get ComType of oAddressEntry to sAddressType
If (sAddressType = 'SMTP') Begin
    Get ComName of oAddressEntry to OutlookAddresses[iElement].sValue[iNameCol]
    Get ComAddress of oAddressEntry to OutlookAddresses[iElement].sValue[iAddressCol]
    Increment iElement
End
At the end of the LoadAddresses routine we tell the grid to load the data from the array we just filled.
Code:
Send InitializeData OutlookAddresses
The array is - as usual - an array of tDataSourceRow struct elements. The iNameCol and iAddressCol are integers and their value is retrieved via the piColumnId property of the individual columns of the selection list.
Code:
Get piColumnId of oNameColumn to iNameCol
Get piColumnId of oAddressColumn to iAddressCol
Disconnect Outlook
If you connect to Microsoft Outlook it is fair to disconnect as well when you are ready so in the ClosePromptList message I send a ReleaseComObject message to the oOutlookApplication object.
Code:
Procedure ClosePromptList
    Forward Send ClosePromptList
    Send ReleaseComObject of oOutlookApplication
End_Procedure
Calling the Selection List
For the blog source code I decided the call the selection list from a Form object but you can of course take a different object type if you want. The prompt button mode is turned on, the prompt object is set so that the F4 key can also be used to popup the selection list.
Code:
Object oMailToForm is a Form
    Set Size to 13 242
    Set Location to 5 50
    Set Label to "To:"
    Set Label_Col_Offset to 2
    Set Label_Justification_Mode to JMode_Right
    Set Prompt_Object to oOutlookAddressesLookup
    Set Prompt_Button_Mode to PB_PromptOn
End_Object
If you think this is it... no you need to perform some extra actions. We do this in the Prompt_Callback event. In there we set the peUpdateMode and the properties to login to Microsoft Outlook.
Code:
Procedure Prompt_Callback Integer hPrompt
    Set psProfileName of hPrompt to 'Outlook'
    Set psPassword of hPrompt to '<my secret="" password="">'                
    Set peUpdateMode of hPrompt to umPromptCustom
    Set phmPromptUpdateCallback of hPrompt to (RefProc (DisplaySelectedAddresses))
End_Procedure

Returning Data From the Selection List
The retrieval of the selected addresses is done in the method DisplaySelectedAddresses as you could see in the code above where I assigned the message ID of that routine to the property phmPromptUpdateCallBack. In the procedure we retrieve the selected addresses via the SelectedColumnValues message available in the cCJGridPromptList object.
Code:
Procedure DisplaySelectedAddresses Handle hoPrompt
    String[] sSelectedAddresses
    String sAddresses
    Integer iUpdateCol iElements iElement
    
    Get piUpdateColumn of hoPrompt to iUpdateCol
    Get SelectedColumnValues of hoPrompt iUpdateCol to sSelectedAddresses


    Move (SizeOfArray (sSelectedAddresses)) to iElements
    Decrement iElements
    For iElement from 0 to iElements
        If (iElement > 0) Begin
            Move (sAddresses + ';') to sAddresses
        End
        Move (sAddresses - sSelectedAddresses[iElement]) to sAddresses
    Loop
    
    Set Value to sAddresses
End_Procedure
Because the object used to select the addresses is a single value object (a Form) the addresses are appended to each other separated by a semi-colon.

Conclusion
Another great enhancement was added to Visual DataFlex that makes it again easier to achieve your goal: Make a successful application for happy customers!</my>

Comments

  1. Pieter van Dieren's Avatar
    Thanks for sharing Vincent!
    Would it be a good idea to make the code available for download as well? (E.g. in a workspace)
  2. Ditte's Avatar
    Hi Vincent,

    some days before we have create this sample in a workshop. We are going some other way without success.
    No I have hoped to find the end.
    I missing the connection between:

    Code:
    Get ComAddressEntries of oAddressList to vAddressEntries 
    Set pvComObject of oAddressEntries to vAddressEntries 
    Get ComCount of oAddressEntries to iAddressEntries
     For iAddressEntry from 1 to iAddressEntries     
      Get ComItem of oAddressEntries iAddressEntry to vAddressEntry

    and

    Code:
    Get ComType of oAddressEntry to sAddressType
     If (sAddressType = 'SMTP') Begin     
      Get ComName of oAddressEntry to OutlookAddresses[iElement].sValue[iNameCol]     
      Get ComAddress of oAddressEntry to OutlookAddresses[iElement].sValue[iAddressCol]     
      Increment iElement 
      End
    Some helps needed.

    Regards Dittmar
  3. Francisco's Avatar
    Great !
    But i have some errors
    psProfileName doesn't exist in Outlook.pkg (made from Office 2007)
    and a "Attempt to use object before initialized, pvComObject is NULL" error in the line "Get ComType of oAddressEntry to sAddressType" ...
  4. Francisco's Avatar
    Quote Originally Posted by Francisco
    Great !
    But i have some errors
    psProfileName doesn't exist in Outlook.pkg (made from Office 2007)
    and a "Attempt to use object before initialized, pvComObject is NULL" error in the line "Get ComType of oAddressEntry to sAddressType" ...