Creating the Pocket PIM Sample Application

Team-Fly    

 
eMbedded Visual Basic: Windows CE and Pocket PC Mobile Applications
By Chris Tacke, Timothy Bassett
Table of Contents
Chapter 6.  The Pocket Outlook Object Model: Accessing Contacts and Calendar Items


The sample application for this chapter is Pocket PIM, a version of the native PIM applications with a few twists . Although it's not intended to replace the powerful PIM applications, it presents some interesting features.

This sample application is intended to give a detailed understanding of the more commonly used functionality of POOM. Its design is also intended to give a general understanding of the style to work with POOM.

Pocket PIM features a Contact Grid (see Figure 6.2), an Appointment Grid (see Figure 6.3), and a Task Grid (see Figure 6.4). Each grid can be sorted by any of the displayed fields.

Figure 6.2. The Contacts Form. Notice that the vertical scroll is different from the typical vertical grid scrollbar.

graphics/06fig02.gif

Figure 6.3. The Tasks Form can be sorted by any of the displayed fields.

graphics/06fig03.gif

Figure 6.4. The Appointments Form provides a completely different view of appointments from the native PIM.

graphics/06fig04.gif

Other Technologies Demonstrated

Although not the focus of the chapter, pay attention to two other technologies or architectural styles used in the Pocket PIM application.

First, the manual paging technique used for the grids in the application. When building Pocket PIM, my device had more than 1,000 contacts present. Populating a grid with 1,000 records can be a poorly performing process. The power of the Pocket PC operating system is impressive, especially with a StrongArm processor, but there are limitations and the controls can become sluggish when consuming large amounts of data.

The grids are populated by using a manual-paging method that populates only the rows visible to users. The vertical scrollbar isn't visible, but a vertical scrollbar control is added in its place.

The second technology used is the ability to distinguish between a tap on the grid and whether the stylus is being held down to present a pop-up menu. This is accomplished by using the AsyncKeyState API call.

Building the Application

When first starting the project, be sure to set the reference to the PIMSTORE.DLL as instructed earlier. Let's start by creating the module modGlobal.bas.

modGlobal: Const, Global Objects, and Declares

Add a module named modGlobal to the project. The entire module is shown in Listing 6.6, but the most important aspect is to get the gobjPoom declared. You can create it all now, load it from the book resource files, or create it as you go.

Listing 6.6 modGlobal.bas: Public Variables , Consts, and API Declarations
 Option Explicit Public gobjPoom As PocketOutlook.Application ' number of rows in the contact grid Public Const CONTACT_CONTACTGRID_ROWS = 13 ' used as the captions for the contact grid's "header" row Public Const CONTACTGRID_HEADER_FILEAS = "File As" Public Const CONTACTGRID_HEADER_LASTNAME = "Last Name" Public Const CONTACTGRID_HEADER_FIRSTNAME = "First Name" Public Const CONTACTGRID_HEADER_COMPANYNAME = "Company Name" Public Const CONTACTGRID_HEADER_BUSINESSTELEPHONE = "Work Phone" ' used as the sort order for the contact's sort orders Public Const CONTACTGRID_HEADER_SORT_FILEAS = "FileAs" Public Const CONTACTGRID_HEADER_SORT_LASTNAME = "LastName" Public Const CONTACTGRID_HEADER_SORT_FIRSTNAME = "FirstName" Public Const CONTACTGRID_HEADER_SORT_COMPANYNAME = "CompanyName" Public Const CONTACTGRID_HEADER_SORT_BUSINESSTELEPHONE _              = "BusinessTelephoneNumber" ' defines the column numbers for the contact grid Public Const CONTACTGRID_COL_FILEAS = 0 Public Const CONTACTGRID_COL_LASTNAME = 1 Public Const CONTACTGRID_COL_FIRSTNAME = 2 Public Const CONTACTGRID_COL_COMPANYNAME = 3 Public Const CONTACTGRID_COL_BUSINESSTELEPHONE = 4 ' the index of the menubars for the contact grid's popup menu Public Const CONTACTPOPUP_MENU_INDEX_NEW = 1 Public Const CONTACTPOPUP_MENU_INDEX_DELETE = 2 Public Const CONTACTPOPUP_MENU_INDEX_COPY = 3 Public Const CONTACTPOPUP_MENU_INDEX_SHOWAPPTS = 4 Public Const CONTACTPOPUP_MENU_INDEX_SAMECOMP = 5 Public Const CONTACTPOPUP_MENU_INDEX_BEAM = 6 ' captions for the menubars for the contact grid's popup menu Public Const CONTACTPOPUP_MENU_CAPTION_NEW = "New Contact" Public Const CONTACTPOPUP_MENU_CAPTION_DELETE = "Delete Contact" Public Const CONTACTPOPUP_MENU_CAPTION_COPY = "Copy Contact" Public Const CONTACTPOPUP_MENU_CAPTION_SHOWAPPTS = "Appointments for Contact" Public Const CONTACTPOPUP_MENU_CAPTION_SAMECOMP = "New Contact-Same Company" Public Const CONTACTPOPUP_MENU_CAPTION_BEAM = "Beam Contact" ' key for the menubars Public Const MENU_VIEW_CONTACTS = "CONTACTS" Public Const MENU_VIEW_TASKS = "TASKS" Public Const MENU_VIEW_APPOINTMENT = "APPOINTMENT" ' key for the menubars Public Const MENU_VIEW_CAPTION = "View" Public Const MENU_VIEW_CONTACTS_CAPTION = "Contacts" Public Const MENU_VIEW_TASKS_CAPTION = "Tasks" Public Const MENU_VIEW_APPOINTMENT_CAPTION = "Appointments" ' used as the captions for the appt grid Public Const APPTGRID_HEADER_START = "Start" Public Const APPTGRID_HEADER_SUBJECT = "Subject" Public Const APPTGRID_HEADER_LOCATION = "Location" Public Const APPTGRID_HEADER_SORT_START = "Start" Public Const APPTGRID_HEADER_SORT_SUBJECT = "Subject" Public Const APPTGRID_HEADER_SORT_LOCATION = "Location" Public Const APPTGRID_COL_START = 0 Public Const APPTGRID_COL_SUBJECT = 1 Public Const APPTGRID_COL_LOCATION = 2 Public Const APPT_APPTGRID_ROWS = 14 Public Const APPTPOPUP_MENU_INDEX_NEW = 1 Public Const APPTPOPUP_MENU_INDEX_DELETE = 2 Public Const APPTPOPUP_MENU_INDEX_COPY = 3 Public Const APPTPOPUP_MENU_CAPTION_NEW = "New Appt" Public Const APPTPOPUP_MENU_CAPTION_DELETE = "Delete Appt" Public Const APPTPOPUP_MENU_CAPTION_COPY = "Copy Appt" Public Const TASKGRID_COL_PRIORITY = 0 Public Const TASKGRID_COL_SUBJECT = 1 Public Const TASKGRID_COL_DUEDATE = 2 Public Const TASKGRID_HEADER_PRIORITY = "Priority" Public Const TASKGRID_HEADER_SUBJECT = "Subject" Public Const TASKGRID_HEADER_DUEDATE = "Due Date" Public Const TASKGRID_HEADER_SORT_PRIORITY = "Importance" Public Const TASKGRID_HEADER_SORT_SUBJECT = "Subject" Public Const TASKGRID_HEADER_SORT_DUEDATE = "DueDate" Public Const TASKPOPUP_MENU_INDEX_NEW = 1 Public Const TASKPOPUP_MENU_INDEX_DELETE = 2 Public Const TASKPOPUP_MENU_INDEX_COPY = 3 Public Const TASKPOPUP_MENU_INDEX_BEAM = 4 Public Const TASKPOPUP_MENU_CAPTION_NEW = "New Task" Public Const TASKPOPUP_MENU_CAPTION_DELETE = "Delete Task" Public Const TASKPOPUP_MENU_CAPTION_COPY = "Copy Task" Public Const TASKPOPUP_MENU_CAPTION_BEAM = "Beam Task" Public Const TASK_TASKGRID_ROWS = 13 ' used to tell if the stylus is down... Public Const ASYNCKEYSTATE_LEFTMOUSE_KEY = 1 Public Const MOUSEDOWN_RIGHTCLICK_TIMING = 0.5 Public Declare Function CreatePopupMenu Lib "Coredll" () As Long Public Declare Function DestroyMenu Lib "Coredll" _        (ByVal hMenu As Long) As Long Public Declare Function AppendMenu Lib "Coredll" Alias "AppendMenuW" _        (ByVal hMenu As Long, ByVal wFlags As Long, _         ByVal wIDNewItem As Long, ByVal lpNewItem As String) As Long Public Declare Function TrackPopupMenuEx Lib "Coredll" _        (ByVal hMenu As Long, ByVal un As Long, ByVal n1 As Long, _         ByVal n2 As Long, ByVal hWnd As Long, lpTPMParams As Long) As Long Public Declare Function GetAsyncKeyState Lib "Coredll" _        (ByVal vKey As Long) As Integer Public Const MF_ENABLED = &H0& Public Const MF_STRING = &H0& Public Const TPM_TOPALIGN = &H0& Public Const TPM_LEFTALIGN = &H0& Public Const TPM_RETURNCMD = &H100& Public Const MF_GRAYED = &H1& Public Const MF_CHECKED = &H8& Public Const MF_UNCHECKED = &H0& Public Const MF_SEPARATOR = &H800& 
Contacts Form

Add a form to the project and name it frmContact. Add a grid and a vertical scrollbar. Name the grid grdContact and leave the scrollbar its default name of Vscroll1.

Add nine command buttons, sizing and placing similar to Figure 6.2. Give them captions of #ab, cde, fgh, ijk, lmn, opq, rst, uvw, and xyz. Name them cmdAB, cmdCDE, cmdFGH, cmdIJK, cmdLMN, cmdOPQ, cmdRST, cmdUVW, and cmdXYZ, respectively. To get the buttons white, you need to change the Style property to vbButtonGraphical and the BackColor to WindowBackground.

These buttons are designed to allow users to jump to a particular contact within the sort order. For example, if the current sort order is LastName, tapping the FGH button will locate the grid to the first contact that has a LastName that begins with F.

Contact Form Level Variables

There are two form level variables for the Contact form:

 Dim gContactRowData(13) As Long Dim gstrContactSortOrder As String 

The array, dimensioned with 13 elements, stores the OID for each contact displayed in the grid. The current sort order for the grid is stored in the string variable gstrContactSortOrder.

The Form_Load Event

The first method to explore is the Form_Load event (see Listing 6.7). The Contact form should be the first form loaded, so in it, we'll create the POOM object and Logon() to it. For development purposes, it will check if there are any contacts and if not, prompt you to create some random ones. The rest of the code does other initialization.

Listing 6.7 The Form_Load Event
 Private Sub Form_Load()     Dim objContacts As PocketOutlook.Items     Dim intCol As Integer     ' create the PocketOutlook application once and only once...     Set gobjPoom = CreateObject("PocketOutlook.Application")     ' logon - can not use until Logon is called...     gobjPoom.Logon     ' get all contacts     Set objContacts = gobjPoom.GetDefaultFolder(olFolderContacts).Items     ' check to see if there are any contacts     If objContacts.Count = 0 Then         ' if no contacts, prompt user (developer) to create some         If MsgBox("There are no contacts, would you like to add some?", _                   vbYesNo, "Create Contacts") = vbYes Then                     ' create dummy contacts                     MakeContacts         End If     End If     ' set the default sort order to the FileAs column     gstrContactSortOrder = CONTACTGRID_HEADER_SORT_FILEAS     ' one row in grid - for the header     Me.grdContacts.Rows = 1     ' five columns in grid     Me.grdContacts.Cols = 5     ' set the column widths     Me.grdContacts.ColWidth(CONTACTGRID_COL_FILEAS) = 1500     Me.grdContacts.ColWidth(CONTACTGRID_COL_LASTNAME) = 1500     Me.grdContacts.ColWidth(CONTACTGRID_COL_FIRSTNAME) = 1500     Me.grdContacts.ColWidth(CONTACTGRID_COL_COMPANYNAME) = 1500     Me.grdContacts.ColWidth(CONTACTGRID_COL_BUSINESSTELEPHONE) = 1500     ' set the header row captions     Me.grdContacts.TextMatrix(0, CONTACTGRID_COL_FILEAS) = _         CONTACTGRID_HEADER_FILEAS     Me.grdContacts.TextMatrix(0, CONTACTGRID_COL_LASTNAME) = _         CONTACTGRID_HEADER_LASTNAME     Me.grdContacts.TextMatrix(0, CONTACTGRID_COL_FIRSTNAME) = _         CONTACTGRID_HEADER_FIRSTNAME     Me.grdContacts.TextMatrix(0, CONTACTGRID_COL_COMPANYNAME) = _         CONTACTGRID_HEADER_COMPANYNAME     Me.grdContacts.TextMatrix(0, CONTACTGRID_COL_BUSINESSTELEPHONE) = _         CONTACTGRID_HEADER_BUSINESSTELEPHONE     ' set the row to 0 - so we can manipulate the header cells     Me.grdContacts.Row = 0     ' set the fonts for the header to bold     For intCol = 0 To Me.grdContacts.Cols - 1         Me.grdContacts.Col = intCol         Me.grdContacts.CellFontBold = True     Next ' set the column back to 0     Me.grdContacts.Col = 0 End Sub 

Let's dissect this method and look at different segments of it.

Initializing POOM

In the following code snippet, the variable objContacts is declared as PocketOutlook.Items to retain a reference to the Contacts folder items. The POOM object is created through the CreateObject statement; remember that the declaration for it, gobjPOOM, was declared as Public in the modGlobal module file. After the POOM object is created, it's initialized by called the Logon method.

 Dim objContacts As PocketOutlook.Items Dim intCol As Integer ' create the PocketOutlook application once and only once.... Set gobjPoom = CreateObject("PocketOutlook.Application") ' logon - can not use until Logon is called... gobjPoom.Logon 
Checking for Contacts and Creating Samples

Rather than just get a reference to a PocketOutlook.Folder, a reference is taken directly to the Items collection from the Folder object, as shown in the following code. This reference, set to objContacts, was declared in the preceding snippet as PocketOutlook.Items. The Count property is tested for zero and if so, users are prompted to create some contacts. If users answer Yes, the method MakeContacts is called to create the contacts. (We'll explore MakeContacts after discussion the Form_Load event.)

Note

If your Pocket PC creates a default contact or appointment, you may have to change the check of Count = 0. The HP Jornada will create a contact and a task after a hard reset.


 ' get all contacts Set objContacts = gobjPoom.GetDefaultFolder(olFolderContacts).Items ' check to see if there are any contacts If objContacts.Count = 0 Then     ' if no contacts, prompt user (developer) to create some     If MsgBox("There are no contacts, would you like to add some?", _                vbYesNo, "Create Contacts") = vbYes Then                    ' create dummy contacts                    MakeContacts     End If End If 
Setting the Sort Order

The default current sort order is stored to the variable gstrContactSortOrder. This value comes again from a Const from the modGlobal module:

 ' set the default sort order to the FileAs column gstrContactSortOrder = CONTACTGRID_HEADER_SORT_FILEAS 
Setting Up the Grid

Next, the grid is set up to have the correct number of rows and columns:

 ' one row in grid - for the header Me.grdContacts.Rows = 1 ' five columns in grid Me.grdContacts.Cols = 5 

Then the column widths are set:

 ' set the column widths Me.grdContacts.ColWidth(CONTACTGRID_COL_FILEAS) = 1500 Me.grdContacts.ColWidth(CONTACTGRID_COL_LASTNAME) = 1500 Me.grdContacts.ColWidth(CONTACTGRID_COL_FIRSTNAME) = 1500 Me.grdContacts.ColWidth(CONTACTGRID_COL_COMPANYNAME) = 1500 Me.grdContacts.ColWidth(CONTACTGRID_COL_BUSINESSTELEPHONE) = 1500 

The "virtual" headers are created:

 ' set the header row captions Me.grdContacts.TextMatrix(0, CONTACTGRID_COL_FILEAS) = _     CONTACTGRID_HEADER_FILEAS Me.grdContacts.TextMatrix(0, CONTACTGRID_COL_LASTNAME) = _     CONTACTGRID_HEADER_LASTNAME Me.grdContacts.TextMatrix(0, CONTACTGRID_COL_FIRSTNAME) = _     CONTACTGRID_HEADER_FIRSTNAME Me.grdContacts.TextMatrix(0, CONTACTGRID_COL_COMPANYNAME) = _     CONTACTGRID_HEADER_COMPANYNAME Me.grdContacts.TextMatrix(0, CONTACTGRID_COL_BUSINESSTELEPHONE) = _     CONTACTGRID_HEADER_BUSINESSTELEPHONE 

After the headers are created, they are made bold by moving to the first row (Row = 0) and then cycling through the columns and setting the font to bold:

 ' set the row to 0 - so we can manipulate the header cells Me.grdContacts.Row = 0 ' set the fonts for the header to bold     ' set the fonts for the header to bold     For intCol = 0 To Me.grdContacts.Cols - 1         Me.grdContacts.Col = intCol         Me.grdContacts.CellFontBold = True     Next     ' set the column back to 0     Me.grdContacts.Col = 0 
Loading the Menu

Finally, the menu is populated:

 ' set up the menu LoadMenu 

Although the menu code is present in the code resources, see Chapter 4, "Working with Menu Controls for Pocket PC," for a description of menu functionality. If you don't want to implement the menu now, omit this code line or comment it out.

The MakeContacts Routine

The MakeContacts routine (see Listing 6.8) is designed to allow you to have a group of contacts that can be manipulated without affecting your actual contacts. If you plan to use MakeContacts, be sure that your device has been hard-reset and is configured in ActiveSync to not sync Contacts.

Listing 6.8 Making Sample Contacts
 Sub MakeContacts()     Dim objContact As PocketOutlook.ContactItem     Dim objItemsContact As PocketOutlook.Items     Dim intContact As Integer     Dim intProperty As Integer     Dim intChar As Integer     Dim strLetter As String     ' get the contact folder     Set objItemsContact = gobjPoom.GetDefaultFolder(olFolderContacts).Items     ' seed the RND() function     Rnd Timer()     ' add 100 contacts     For intContact = 1 To 100         ' get the new contact         Set objContact = objItemsContact.Add()         ' set 3 different properties         For intProperty = 1 To 3             ' make a string up to 15 chars in length             For intChar = 1 To Int(Rnd() * 15)                 ' make the letter                 strLetter = Chr((Rnd() * 26) + 63)                 ' is it alpha?                 If strLetter >= "A" And strLetter <= "Z" Then                     ' which property are we setting                     Select Case intProperty                         Case 1                             ' set the FirstName property                             objContact.FirstName = objContact.FirstName & strLetter                         Case 2                             ' set the LastName property                             objContact.LastName = objContact.LastName & strLetter                         Case 3                             ' set the CompanyName property                             objContact.CompanyName = objContact.CompanyName & strLetter                     End Select                 End If             Next         Next         ' we need to ensure there is at least something         ' in one of the Properties         If Len(objContact.FirstName & objContact.LastName) = 0 Then             objContact.FirstName = "Bob"         End If         ' set the email property         objContact.Email1Address = objContact.FirstName & "@rtmobile.com"         ' save the contact         objContact.Save     Next End Sub 

Let's dissect MakeContacts, covering the new ground.

First, a reference to the ContactFolder.Items is retrieved:

 ' get the contact folder Set objItemsContact = gobjPoom.GetDefaultFolder(olFolderContacts).Items 

The following snippet shows an abbreviated version of the loop that creates 100 contacts. An object reference to a newly created ContactItem is retrieved by calling the Add method from the Items collection. Remember that the Items collection is located on the Folder object.

 ' add 100 contacts For intContact = 1 To 100     ' get the new contact     Set objContact = objItemsContact.Add() ...     ' save the contact  objContact.Save  Next 

Next, three properties are set from a randomly created string: FirstName, LastName, and CompanyName.

 Case 1     ' set the FirstName property     objContact.FirstName = objContact.FirstName & strLetter Case 2     ' set the LastName property     objContact.LastName = objContact.LastName & strLetter Case 3     ' set the CompanyName property     objContact.CompanyName = objContact.CompanyName & strLetter 

Something important to remember before attempting to save a ContactItem is that one of the following properties must have a value: FirstName, LastName, FileAs, or CompanyName. A check is performed in the following code to ensure that the contact has a FirstName or LastName value set:

 ' we need to ensure there is at least something ' in one of the Properties If Len(objContact.FirstName & objContact.LastName) = 0 Then     objContact.FirstName = "Bob" End If ' set the email property objContact.Email1Address = objContact.FirstName & "@rtmobile.com" 

After all is well, the Contact is saved via the Save method:

 ' save the contact objContact.Save 
The Form_Activate Event

The Form_Activate event is used simply to refresh the contact grid. The method to refresh the grid is RefreshContacts:

 Private Sub Form_Activate()     ' refresh ALL contacts     RefreshContacts "" End Sub 

The RefreshContacts function (see Listing 6.9) is the Contact form's meat-and-potatoes. It's responsible for finding the correct contacts, sorting, and displaying them.

Listing 6.9 The RefreshContacts Function
 Public Function RefreshContacts(ByVal strSeek As String)     Dim objContact As PocketOutlook.ContactItem     Dim objItemsContact As PocketOutlook.Items     Dim intContact As Integer     Dim lngThisContact As Long     Dim strRestrict As String     Dim lngOIDFirstFound As Long     Dim lngFirstContactDisplayed As Long     On Error Resume Next     ' get all contacts     Set objItemsContact = gobjPoom.GetDefaultFolder(olFolderContacts).Items     ' sort by the current sort order - always ascending     objItemsContact.Sort "[" & gstrContactSortOrder & "]", False     ' set the max for the scrollbar to be the top contact     ' of the last "page" of contacts    Me.VScroll1.Max = objItemsContact.Count - Me.VScroll1.LargeChange + 1     ' if we have a seek parameter try to find     ' the first contact meeting that parameter     If Len(strSeek) > 0 Then         ' setup the restrict clause to be equal to or greater than         ' the seek parameter         ' this will find the next contact that meets the seek         ' parameter if one does not exactly meet it         strRestrict = "[" & gstrContactSortOrder & _                       "]>=""" & "" & strSeek & """"         ' apply the seek         Set objContact = objItemsContact.Find(strRestrict)         ' if we found a contact meeting the seek         ' let's store the OID value (primary key - Outlook ID)         If Not objContact Is Nothing Then             ' grab the OID             lngOIDFirstFound = objContact.oid             ' start at the 1st contact             lngThisContact = 1             ' cycle thru the contacts to find the contact's             ' place in the sort order             ' while we still looking at a valid contact             Do While lngThisContact <= objItemsContact.Count                 ' get the contact                 Set objContact = objItemsContact.Item(lngThisContact)                 ' if it's our contact, let's get out                 ' lngThisContact will be the correct index of                 ' the contact found                 If objContact.oid = lngOIDFirstFound Then                     Exit Do                 End If                 lngThisContact = lngThisContact + 1             Loop         End If     Else         ' if we don't have a seek value, use the scroll bar ' current value         lngThisContact = Me.VScroll1.Value     End If     ' set the rows     Me.grdContacts.Rows = CONTACT_CONTACTGRID_ROWS     ' save the index of the first contact displayed     ' this is used to update the scroll bar with the correct     ' position after we're done     lngFirstContactDisplayed = lngThisContact     ' we don't clear the grid here, but just write over top of it     ' this prevents less flickering and better performance     ' if we don't use all the available rows, we just truncate them at the end....     ' we're going to cycle through the contacts     ' and write them to the grid     For intContact = 0 To CONTACT_CONTACTGRID_ROWS - 1         ' if we've gone past the last contact         ' let's get out         ' this happens when you start displaying contacts         ' near the bottom - or when the first contact         ' is closer to the bottom than the number of rows in the grid         If lngThisContact > objItemsContact.Count Then             Exit For         End If         ' get the contact         Set objContact = objItemsContact.Item(lngThisContact)         ' display the fields         Me.grdContacts.TextMatrix(intContact + 1, _             CONTACTGRID_COL_FILEAS) = objContact.FileAs         Me.grdContacts.TextMatrix(intContact + 1, _             CONTACTGRID_COL_LASTNAME) = objContact.LastName         Me.grdContacts.TextMatrix(intContact + 1, _             CONTACTGRID_COL_FIRSTNAME) = objContact.FirstName         Me.grdContacts.TextMatrix(intContact + 1, _             CONTACTGRID_COL_COMPANYNAME) = objContact.CompanyName         Me.grdContacts.TextMatrix(intContact + 1, _             CONTACTGRID_COL_BUSINESSTELEPHONE) = _             objContact.BusinessTelephoneNumber         ' save the OID into the array         ' this is used to do an action on the contact such as delete,         ' copy, display, etc...         ' we would use the Grid.RowData collection here, but there seems         ' to be some bug that it will not take the value         gContactRowData(intContact + 1) = objContact.oid         ' increment the index of the contact to be displayed         lngThisContact = lngThisContact + 1     Next     ' if we displayed less contacts than the grid allows     ' we need to truncate the dead rows     ' the -1 and +1 accounts for the header row     If intContact < CONTACT_CONTACTGRID_ROWS - 1 Then         Me.grdContacts.Rows = intContact + 1     End If End Function 

RefreshContacts takes a single string parameter, strSeek:

 Public Function RefreshContacts(ByVal strSeek As String) 

This parameter is used when one button is tapped. It provides the criteria on which to seek in the current sort order. In other words, when the grid is sorted by Last Name and the fgh button is tapped, strSeek will have a value of F, meaning to start displaying contacts where the Last Name begins with F.

Now let's look at the functionality of RefreshContacts piece by piece.

Populating and Sorting the Grid Rows

Let's jump to the main loop that displays the Contacts and look at it:

 ' we're going to cycle through the contacts ' and write them to the grid  For intContact = 0 To CONTACT_CONTACTGRID_ROWS - 1  ' if we've gone past the last contact     ' let's get out     ' this happens when you start displaying contacts     ' near the bottom - or when the first contact     ' is closer to the bottom than the number of rows in the grid  If lngThisContact > objItemsContact.Count Then   Exit For   End If  ' get the contact  Set objContact = objItemsContact.Item(lngThisContact)  ' display the fields  Me.grdContacts.TextMatrix(intContact + 1, _   CONTACTGRID_COL_FILEAS) = objContact.FileAs   Me.grdContacts.TextMatrix(intContact + 1, _   CONTACTGRID_COL_LASTNAME) = objContact.LastName   Me.grdContacts.TextMatrix(intContact + 1, _   CONTACTGRID_COL_FIRSTNAME) = objContact.FirstName   Me.grdContacts.TextMatrix(intContact + 1, _   CONTACTGRID_COL_COMPANYNAME) = objContact.CompanyName   Me.grdContacts.TextMatrix(intContact + 1, _   CONTACTGRID_COL_BUSINESSTELEPHONE) = objContact.BusinessTelephoneNumber  ' save the OID into the array     ' this is used to do an action on the contact such as delete,     ' copy, display, etc...     ' we would use the Grid.RowData collection here, but there seems     ' to be some bug that it will not take the value  gContactRowData(intContact + 1) = objContact.oid  ' increment the index of the contact to be displayed  lngThisContact = lngThisContact + 1  Next 

The intContact loop cycles through once for each nonheader row in the grid. The variable lngThisContact is used as an index to the contact Items collection. After a refresh of all contacts starting at the top of the sort order, lngThisContact equals 1 at the start of the loop. If you refresh starting later in the sort order, it will contain a different value. We'll investigate that later in this chapter.

The first test completed is a check to be sure the index value in lngThisContact hasn't exceeded the Count in the contact folder Items collection. If this index value has exceeded the Count, the loop is exited.

After this index value is verified to be valid, a reference to the ContactItem existing at that index value in the Items collection is retrieved to the variable objContact, declared as a PocketOutlook.ContactItem.

This ContactItem is then used to populate a row in the grid using the grid's TextMatrix array. The key to the itemthe OID property of the ContactItemis then stored to the respective row of the array gContactRowData. This creates a one-to-one relationship between the grid row and the row in the array. Typically, the grid's RowData array would be used for this purpose, but there seems to be an inconsistent bug with the RowData array when trying to store certain OID values.

Lastly, in the loop, the index value in lngThisContact is incremented by one, ready for the next ContactItem in the contact folder's Items collection.

Let's look more closely at some of the setup before the display loop and the filtering of the ContactItems:

 ' get all contacts Set objItemsContact = gobjPoom.GetDefaultFolder(olFolderContacts).Items 

First, objItemsContactthe object reference for a PocketOutlook.Items collection used to populate the gridis set equal to an Items collection from the ContactItem folder via the GetDefaultFolder(olFolderContacts).Items collection. This retrieves all contacts that exist in the ContactItem folder.

Then, these items are sorted into the requested order by using the Sort method:

 ' sort by the current sort order - always ascending objItemsContact.Sort "[" & gstrContactSortOrder & "]", False 

Here, the public variable gstrContactSortOrder contains the currently requested sort order property name (for example FileAs, LastName, or CompanyName). Remember, the default sort order is assigned in the Form_Load event as FileAs.

Updating the Scrollbar

Because you are using manual-paging for the grid, you need to manually control the scrollbar Max property:

 ' set the max for the scrollbar to be the top contact ' of the last "page" of contacts Me.VScroll1.Max = objItemsContact.Count - Me.VScroll1.LargeChange + 1 

At this point, you know how many contacts exist and set the VScroll1.Max property to objItemsContact.Count (contact item count), minus the scrollbar's LargeChange value plus one. This formula allows navigation to the first contact by sliding the scrollbar completely to the top, yielding a Value of 1. Then, the LargeChange value plus 1 is subtracted from the objItemsContact.Count value to show the last page of contacts by sliding the scrollbar completely to the bottom. When the scrollbar is completely at the bottom, it's Value is the index of the first contact on the last page of contact items. If you simply set the Max value to the contact items Count value, only the last contact in the collection is displayed when the scrollbar is slid all the way to the bottom; this would waste the rest of the rows in the grid.

Seeking the First Matching Contact

Next, the control-flow checks to see whether there's a seek value (strSeek) as to where to locate the grid in the collection of contact items by the current sort order. This value can be passed by any command buttons placed above the grid control. (The following code to handle the seek value is abbreviated from the Listing 6.9.) If a seek value isn't provided, the contact index (lngThisContact) is specified by the scrollbar's Value.

 ' if we have a seek parameter try to find ' the first contact meeting that parameter If Len(strSeek) > 0 Then ... Else     ' if we don't have a seek value, use the scroll bar ' current value     lngThisContact = Me.VScroll1.Value End If 

Let's see what was abbreviated from the previous example that handles the seek value. Here, the code inside the If...Then control-flow block is listed (up to the Else statement):

 If Len(strSeek) > 0 Then     ' setup the restrict clause to be equal to or greater than     ' the seek parameter     ' this will find the next contact that meets the seek     ' parameter if one does not exactly meet it  strRestrict = "[" & gstrContactSortOrder & "]>=""" & "" & strSeek & """"  ' apply the seek  Set objContact = objItemsContact.Find(strRestrict)   ...  Else 

This code block handles receiving the seek value (strSeek). First, the restrict clause (used by the Find method) is constructed . It consists of the proper formatting brackets ([]) around the current sort order (provided by the public variable gstrContactSortOrder). Because the sort order can be dynamically changed, you need to seek its value. This restrict clause is completed by adding the strSeek value inside quotes. Some examples of the clause are

  • [FileAs] >= "F", where the sort order is the FileAs property and the button tapped is FGH

  • [LastName] >= "R", where the sort order is the LastName property and the button tapped is RST

  • [CompanyName] >= " ", meaning to go to the top of the Company sort order

Then, this seek is applied by using it as the parameter for the Find method. If a contact meets this criteria, the Find method will return the first contact meeting it. If no contacts meet this criteria, objContact will be set to Nothing.

Next, let's investigate what happens when a contact is returned from the Find method:

 ' if we found a contact meeting the seek ' let's store the OID value (primary key - Outlook ID)  If Not objContact Is Nothing Then  ' grab the OID  lngOIDFirstFound = objContact.oid  ' start at the 1st contact  lngThisContact = 1  ' cycle thru the contacts to find the contact's     ' place in the sort order     ' while we're still looking at a valid contact  Do While lngThisContact <= objItemsContact.Count  ' get the contact  Set objContact = objItemsContact.Item(lngThisContact)  ' if it's our contact, let's get out         ' lngThisContact will be the correct index of         ' the contact found  If objContact.oid = lngOIDFirstFound Then   Exit Do   End If   lngThisContact = lngThisContact + 1  Loop End If 

A check is completed to ensure that a valid ContactItem is returned to the object reference objContact from the call to the Find method. If objContact has a valid ContactItem reference, the value of its OID property is stored to a Long variable, lntOIDFirstFound. This value is kept to compare each ContactItem object within the collection to the first one's place within the sort order.

Note

You are not filtering the contacts, but just jumping to the first one that meets the criteria. This not only allows the grid to be scrolled above the contact that meets the criteria, but also to continue beyond those that do.


Next, the index variable, lngThisContact, is set equal to 1. This index value is incremented by 1 for each contact that you cycle through to find the index value of the first contact that met the criteria. Imagine that you have a list of contacts sorted by some property stacked from top to bottom. You know what the first contact that meets the criteria is, but you don't know where it falls in the sort order.

Through the lngThisContact index variable, as the test case of being less than or equal to the number of contacts, objItemsContact.Count, begin a Do While loop. Inside this loop retrieve a reference to each ContactItem by its index number, using lngThisContact. A comparison is then made between each ContactItem OID value and the one from the first contact that met the criteria (as stored in the variable lgnOIDFirstFound). If the OID is the same, you have again found the ContactItem in the collection and now have its position. The position is the value of lngThisContact, the index variable, so exit the Do While loop with an Exit Do statement.

So, whether you have a seek value or are dealing with just the value from the scrollbar, you know where to start displaying the contacts.

Resetting the Grid

The next two items before entering the display loop are straightforward, as shown in the following snippet. You reset the grid to the maximum number of rows available. This is important only after you seek a value near the bottom of the sort order and may have truncated some unused rows. Secondly, now that you know which contact (by the index value) will be displayed, store this value off to lngFirstContactDisplayed. This allows the scrollbar Value to be updated when you complete the displaying of the contacts.

 ' set the rows  Me.grdContacts.Rows = CONTACT_CONTACTGRID_ROWS  ' save the index of the first contact displayed ' this is used to update the scroll bar with the correct ' position after we're done  lngFirstContactDisplayed = lngThisContact  

After all the contacts are displayed in the grid, as presented in Listing 6.9 and repeated as follows , a simple test is completed to ensure that we aren't leaving unused rows visible. If we exit the display loop before using all the grid rows, we simply adjust the number of rows to the number of contacts displayed (intContact) plus one. The extra row is to accommodate the header.

 ' if we displayed less contacts than the grid allows ' we need to truncate the dead rows ' the -1 and +1 accounts for the header row  If intContact < CONTACT_CONTACTGRID_ROWS - 1 Then   Me.grdContacts.Rows = intContact + 1   End If  

In summary, the RefreshContacts method displays contacts from the Contact PIM store by using the PocketOutlook.Items and PocketOutlook.ContactItem. It can display contacts based on the scrollbar's position (ScrollBar.Value) or by using a seek value provided from one of the jump buttons. It uses an optimized manual-paging method to display only the number of contacts visible in the grid.

Try this code. At this point, your application should be able to display the first page of contacts.

The Vscroll1_Change Routine

If your application is currently functional to display the first page of contacts, let's add the capability to move through the contacts using the scrollbar:

 Private Sub VScroll1_Change()     ' refresh the contact grid with no seek value     RefreshContacts "" End Sub 
The Jump Buttons

Having RefreshContacts structured to accept a seek value, you now need to implement the jump buttons (see Listing 6.10). These buttons were added to the form when you added the other controls.

Listing 6.10 The Event Code for Jump Buttons
 Private Sub cmdAB_Click()     'refresh contacts using the space character as the first record to display     RefreshContacts " " End Sub Private Sub cmdCDE_Click()     ' refresh contacts using the "C" as the first record to display     RefreshContacts "C" End Sub Private Sub cmdFGH_Click()     ' refresh contacts using the "F" as the first record to display     RefreshContacts "F" End Sub Private Sub cmdIJK_Click()     ' refresh contacts using the "I" as the first record to display     RefreshContacts "I" End Sub Private Sub cmdLMN_Click()     ' refresh contacts using the "L" as the first record to display     RefreshContacts "L" End Sub Private Sub cmdOPQ_Click()     ' refresh contacts using the "O" as the first record to display     RefreshContacts "O" End Sub Private Sub cmdRST_Click()     ' refresh contacts using the "R" as the first record to display     RefreshContacts "R" End Sub Private Sub cmdUVW_Click()     ' refresh contacts using the "U" as the first record to display     RefreshContacts "U" End Sub Private Sub cmdXYZ_Click()     ' refresh contacts using the "X" as the first record to display     RefreshContacts "X" End Sub 

Listing 6.10 has all the event code for the Click events for all the jump buttons. Each jump button calls RefreshContacts with the string value of the first contact to be displayed in the sort order. In other words, if the current sort order is LastName, tapping the button cmdLMN will call RefreshContacts "L", thus displaying the first contact whose LastName value starts with L or greater. The only unique jump button is cmdAB, which should start at any numbers or symbols when it calls RefreshContacts; a space character (Chr(32)) is used for this purpose, because in the sort order, all readable characters come after the space character.

Test the application again. You should now be able to jump to different contacts using the jump buttons.

The Grid_Click Event

You can implement two different sets of functionalities by tapping the grid:

  • Implement the capability to tap the grid to re-sort the contacts or open a particular contact.

  • Use some API calls to detect a held-down stylus to present a shortcut (or pop-up) menu.

Listing 6.11 presents all the code for the grdContacts_Click event.

Listing 6.11 Re-sorting the Grid or Opening a Contact for Display and Edit
 Private Sub grdContacts_Click()     Dim objContact As PocketOutlook.ContactItem     Dim objFolder As PocketOutlook.Folder     Dim lngOID As Long     On Error Resume Next     ' if clicked on the top row, then     ' the grid is resorted and refreshed     If Me.grdContacts.Row = 0 Then         ' first row clicked         ' set the sort order         Select Case Me.grdContacts.Col             Case CONTACTGRID_COL_FILEAS                 gstrContactSortOrder = CONTACTGRID_HEADER_SORT_FILEAS             Case CONTACTGRID_COL_LASTNAME                 gstrContactSortOrder = CONTACTGRID_HEADER_SORT_LASTNAME             Case CONTACTGRID_COL_FIRSTNAME                 gstrContactSortOrder = CONTACTGRID_HEADER_SORT_FIRSTNAME             Case CONTACTGRID_COL_COMPANYNAME                 gstrContactSortOrder = CONTACTGRID_HEADER_SORT_COMPANYNAME             Case CONTACTGRID_COL_BUSINESSTELEPHONE                 gstrContactSortOrder = _                     CONTACTGRID_HEADER_SORT_BUSINESSTELEPHONE         End Select         ' refresh the contacts         RefreshContacts ""     Else         ' get the id of the contact clicked on         lngOID = gContactRowData(Me.grdContacts.Row)         ' get the contact clicked on         Set objContact = gobjPoom.GetItemFromOid(lngOID)         ' display the contact - used the actual Pocket Outlook form         objContact.Display         ' refresh the changes         RefreshContacts ""     End If End Sub 

One simple control-flow If statement determines whether the tap was on the header row (row = 0) or one of the contact rows. Let's first investigate what happens when the header row is tapped:

 ' first row clicked ' set the sort order Select Case Me.grdContacts.Col     Case CONTACTGRID_COL_FILEAS         gstrContactSortOrder = CONTACTGRID_HEADER_SORT_FILEAS     Case CONTACTGRID_COL_LASTNAME         gstrContactSortOrder = CONTACTGRID_HEADER_SORT_LASTNAME     Case CONTACTGRID_COL_FIRSTNAME         gstrContactSortOrder = CONTACTGRID_HEADER_SORT_FIRSTNAME     Case CONTACTGRID_COL_COMPANYNAME         gstrContactSortOrder = CONTACTGRID_HEADER_SORT_COMPANYNAME     Case CONTACTGRID_COL_BUSINESSTELEPHONE         gstrContactSortOrder = CONTACTGRID_HEADER_SORT_BUSINESSTELEPHONE End Select ' refresh the contacts RefreshContacts "" 

Here, after you have determined it was the header row tapped, a Select Case statement determines which column in the grid was tapped by using the Consts declared in the module modGlobal (as shown earlier in Listing 6.6). The sort order is then set by using the public variable that holds the current sort order, gstrContactSortOrder. Last, the grid is refreshed, starting at the top of the sort order (using the blank string parameter) by calling RefreshContacts.

The following code segment reacts to any other row, besides the header row, in the grid being tapped. It retrieves the OID value from the Public array gContactRowData, which is declared at the form level in the section "Contact Form Level Variables," and stores it to the local long variable lngOID. Remember that the array gContactRowData is populated with the corresponding OID from the ContactItem.OID property value when the grid is populated in the RefreshContacts method.

 ' get the id of the contact clicked on lngOID = gContactRowData(Me.grdContacts.Row) ' get the contact clicked on Set objContact = gobjPoom.GetItemFromOid(lngOID) ' display the contact - used the actual Pocket Outlook form objContact.Display ' refresh the changes RefreshContacts "" 

The OID value is the primary key for the PocketOutlook.Item, and is unique even across object types. This is why the next line of code can retrieve the ContactItem by simply using the GetItemFromOID() method on the PocketOutlook.Application object. Next, the Display method on this object is called, which brings this PIM item up in its native GUI. The item is displayed semimodally, meaning that when the item is displayed, your application pauses execution.

Note

The PIM item displayed is modal to the point that after it's closed via the OK button in the upper-right corner, or the Delete menu on the Tools menubar, control returns to your application. Users can tap the Start icon to start any other application just as when they are directly in your application.


After control from Display is returned, the grid is refreshed using no seek-value; therefore, it stays as the same position within the sort order. There's room for improvement here that you could try to implement. Something not addressed is if the fields of the contact are edited that pertain to the current sort order, that contact row may disappear from the grid as it finds its new placement in the sort order.

The second tap functionality to be implemented is the held-down stylus to present a context-sensitive shortcut (pop-up) menu. This is implemented in the Grid.MouseDown event as presented in Listing 6.12.

Listing 6.12 Determining Whether the Stylus Is Held Down Long Enough to Generate a Shortcut Menu
 Private Sub grdContacts_MouseDown(ByVal Button As Long, _         ByVal Shift As Long, ByVal x As Single, ByVal y As Single)     Dim lngOID As Long     Dim intRow As Integer     Dim sngStart As Single     On Error Resume Next     ' start timing - used to figure out how long the     ' stylus has been down     sngStart = Timer()     ' figure out which row was clicked on     intRow = Fix(y / Me.grdContacts.RowHeightMin)     ' if the header row     If intRow = 0 Then         ' nothing to do for a "right-click" on the header row....         Exit Sub     End If     ' set the grid to that row     Me.grdContacts.Row = intRow     ' get the OID for the row...     lngOID = gContactRowData(Me.grdContacts.Row)     ' while the stylus is down     Do While GetAsyncKeyState(ASYNCKEYSTATE_LEFTMOUSE_KEY) <> 0         If Timer() - sngStart > MOUSEDOWN_RIGHTCLICK_TIMING Then             If GetAsyncKeyState(1) <> 0 Then                 ShowContextMenu _                     x + Me.grdContacts.Left, _                     y + Me.grdContacts.Top, _                     lngOID                 Exit Do             End If         End If     Loop End Sub 

In Listing 6.12, first the current Timer() value is stored to a local single variable, sngStart. This value determines how long the stylus has been held down. We're using a threshold of 0.5 seconds to present the shortcut menu.

Next, the row in which the stylus is present is determined by dividing the Y coordinate (a parameter of the MouseDown event) by the grid's RowHeightMin, which is essentially the RowHeight of all rows in this grid. The result has the decimal places dropped by using the Fix function and are stored to the local variable, intRow. If it's determined that the row where the stylus is held is the header row (row = 0), the routine is exited because there's no defined functionality for a popup from the header row.

The grid's current row is then set to this determined row (intRow). This is completed because the grid won't accomplish this natively until the MouseUp event occurs. After the grid refocuses the specified row, lngOID has the correct OID stored to it by using the array gContactRowData containing the OIDs as populated in RefreshContacts.

Next, use an API call to GetAsyncKeyState in a Do While loop to determine whether the stylus is still down. If the stylus is still down and has been down for 0.5 seconds or more, it is determined that the user desires a shortcut menu and calls ShowContextMenu. Parameters for ShowContextMenu are the x- and y-coordinates to place the menu and the OID of the currently selected contact in the grid.

The ShowContextMenu Routine

ShowContextMenu creates a shortcut (pop-up) menu with as many as six menu prompts (see Figure 6.5). It will determine if a valid OID is passed and add menu prompts that correlate only to a valid contact. After users choose one of the menu prompts, an appropriate action is taken.

Figure 6.5. After holding down the stylus for more than a half second, users see a context-sensitive shortcut menu from which to choose an action to perform on the currently selected contact item.

graphics/06fig05.gif

Listing 6.13 shows the entire code for the ShowContextMenu routine.

Listing 6.13 Showing a Pop-Up Menu
 Private Sub ShowContextMenu(ByVal sngX As Single, _         ByVal sngY As Single, ByVal lngOID As Long)     Dim hMenu As Long     Dim objContact As PocketOutlook.ContactItem     Dim objFolderContact As PocketOutlook.Folder     Dim objRecipient As PocketOutlook.Recipient     Dim strCompanyName As String     Dim blnRefresh As Boolean     Dim lngMenuResult As Long     Dim objInfraredFolder As PocketOutlook.Folder     On Error Resume Next     ' create the menu handle     hMenu = CreatePopupMenu()     ' add prompts     AppendMenu hMenu, MF_ENABLED Or MF_STRING, _         CONTACTPOPUP_MENU_INDEX_NEW, CONTACTPOPUP_MENU_CAPTION_NEW     ' only append the following if we have a valid OID (Primary Key)     If lngOID > 0 Then         AppendMenu hMenu, MF_ENABLED Or MF_STRING, _                 CONTACTPOPUP_MENU_INDEX_DELETE, _                 CONTACTPOPUP_MENU_CAPTION_DELETE         AppendMenu hMenu, MF_ENABLED Or MF_STRING, _                 CONTACTPOPUP_MENU_INDEX_COPY, _                 CONTACTPOPUP_MENU_CAPTION_COPY         AppendMenu hMenu, MF_ENABLED Or MF_STRING, _                 CONTACTPOPUP_MENU_INDEX_SHOWAPPTS, _                 CONTACTPOPUP_MENU_CAPTION_SHOWAPPTS         AppendMenu hMenu, MF_ENABLED Or MF_STRING, _                 CONTACTPOPUP_MENU_INDEX_SAMECOMP, _                 CONTACTPOPUP_MENU_CAPTION_SAMECOMP         AppendMenu hMenu, MF_ENABLED Or MF_STRING, _                 CONTACTPOPUP_MENU_INDEX_BEAM, _                 CONTACTPOPUP_MENU_CAPTION_BEAM     End If     ' show the menu and return the choice chosen     lngMenuResult = TrackPopupMenuEx(_                             hMenu, _                             TPM_LEFTALIGN Or TPM_TOPALIGN Or TPM_RETURNCMD, _                             sngX / Screen.TwipsPerPixelX, _                             sngY / Screen.TwipsPerPixelY, _                             frmContact.hWnd, _                             0)     ' get the contacts folder collection     Set objFolderContact = gobjPoom.GetDefaultFolder(olFolderContacts)     ' pick the correct action     Select Case lngMenuResult         Case CONTACTPOPUP_MENU_INDEX_NEW             ' Adding a contact, so let's make sure to refresh afterwards             blnRefresh = True             ' Create the new contact             Set objContact = objFolderContact.Items.Add()             ' display it for editing             objContact.Display         Case CONTACTPOPUP_MENU_INDEX_DELETE             ' deleting a contact, so let's make sure to refresh afterwards             blnRefresh = True             ' get the contact by its id             Set objContact = gobjPoom.GetItemFromOid(lngOID)             ' delete the contact             objContact.Delete         Case CONTACTPOPUP_MENU_INDEX_COPY             ' copying a contact, so let's make sure to refresh afterwards             blnRefresh = True             ' get the contact by its id             Set objContact = gobjPoom.GetItemFromOid(lngOID)             ' make a copy             Set objContact = objContact.Copy()             ' display it for editing             objContact.Display         Case CONTACTPOPUP_MENU_INDEX_SHOWAPPTS             ' show the appt form - giving it the OID (primary-key)             frmAppt.ShowApptForContact (lngOID)         Case CONTACTPOPUP_MENU_INDEX_SAMECOMP             ' basically copying a contact, so let's make sure             ' to refresh afterwards             blnRefresh = True             ' get the contact by its id             Set objContact = gobjPoom.GetItemFromOid(lngOID)             ' save the company name             strCompanyName = objContact.CompanyName             ' create a new contact and store back             ' to the same object reference             Set objContact = objFolderContact.Items.Add()             ' set the company name             objContact.CompanyName = strCompanyName             ' save the contact before displaying, else error occurs             objContact.Save             ' display the contact             objContact.Display         Case CONTACTPOPUP_MENU_INDEX_BEAM             ' get the contact by its id             Set objContact = gobjPoom.GetItemFromOid(lngOID)             ' create infrared folder             Set objInfraredFolder = gobjPoom.GetDefaultFolder(olFolderInfrared)             ' add the contact to the folder             objInfraredFolder.AddItemToInfraredFolder olContactItem, objContact             ' send the folder (contact)             objInfraredFolder.SendToInfrared     End Select     ' release the menu resources     DestroyMenu hMenu     ' if we did something that requires, let's refresh     If blnRefresh Then         ' refresh with no find         RefreshContacts ""     End If End Sub 

Let's break down Listing 6.13 and examine it.

Using API Calls with the Pop-Up Menu

The following lists the code to create, populate, show, and return the result from the pop-up menu. For reference on all APIs used in the shortcut menu, see Chapter 9, "Harnessing the Windows CE API." All these API functions are declared in the module modGlobal (refer to Listing 6.5). CreatePopupMenu returns a long handle to the menu, which is stored to the local variable hMenu.

 ' create the menu handle hMenu = CreatePopupMenu() ' add prompts AppendMenu hMenu, MF_ENABLED Or MF_STRING, _     CONTACTPOPUP_MENU_INDEX_NEW, CONTACTPOPUP_MENU_CAPTION_NEW ' only append the following if we have a valid OID (Primary Key) If lngOID > 0 Then     AppendMenu hMenu, MF_ENABLED Or MF_STRING, _         CONTACTPOPUP_MENU_INDEX_DELETE, CONTACTPOPUP_MENU_CAPTION_DELETE     AppendMenu hMenu, MF_ENABLED Or MF_STRING, _         CONTACTPOPUP_MENU_INDEX_COPY, CONTACTPOPUP_MENU_CAPTION_COPY     AppendMenu hMenu, MF_ENABLED Or MF_STRING, _         CONTACTPOPUP_MENU_INDEX_SHOWAPPTS, CONTACTPOPUP_MENU_CAPTION_SHOWAPPTS     AppendMenu hMenu, MF_ENABLED Or MF_STRING, _         CONTACTPOPUP_MENU_INDEX_SAMECOMP, CONTACTPOPUP_MENU_CAPTION_SAMECOMP     AppendMenu hMenu, MF_ENABLED Or MF_STRING, _         CONTACTPOPUP_MENU_INDEX_BEAM, CONTACTPOPUP_MENU_CAPTION_BEAM End If ' show the menu and return the choice chosen lngMenuResult = TrackPopupMenuEx(_                         hMenu, _                         TPM_LEFTALIGN Or TPM_TOPALIGN Or TPM_RETURNCMD, _                         sngX / Screen.TwipsPerPixelX, _                         sngY / Screen.TwipsPerPixelY, _                         frmContact.hWnd, _                         0) 

The first prompt, for a New Contact, is always added to the pop-up menu, because it doesn't depend on a contact row being selected.

If the OID is valid, the additional prompts are added that are context-sensitive: Delete Contact, Copy Contact, Show Appointments For Contact, New ContactSame Company, and Beam.

Lastly, the shortcut menu is shown and its results are returned and stored to the local long variable lngMenuResult.

Reacting to User Selection

Next, let's examine the actions taken when the user chooses one of the menu options. The following code sets the object reference variable to a PocketOutlook.Folder by using the public gobjPoom PocketOutlook.Application's GetDefaultFolder. This folder contains all contacts and is created here because most of the actions in the Select Case structure below it will take advantage of it.

 ' get the contacts folder collection Set objFolderContact = gobjPoom.GetDefaultFolder(olFolderContacts) 

Next, let's examine the actions taken when users choose one of the menu options. Each of the following actions are taken via a Select Case structure that you can review in Listing 6.13.

Working with Contacts

The following code segment is associated with adding a new contact and displaying it:

 ' Adding a contact, so let's make sure to refresh afterwards blnRefresh = True ' Create the new contact Set objContact = objFolderContact.Items.Add() ' display it for editing objContact.Display 

The Boolean variable blnRefresh is set to True, letting you know that an action has taken place that may affect the grid's contents. A new contact is created by calling the Add method of the PocketOutlook.Items collection object objFolderContact. This reference is held by the local PocketOutlook.ContactItem variable objContact. Lastly, the contact is displayed by using the native Contact application GUI by calling the Display method. (Remember, calling Display is semimodal and your application pauses execution until the user closes the native PIM form.)

The following code deletes the contact:

 ' deleting a contact, so let's make sure to refresh afterwards blnRefresh = True ' get the contact by its id Set objContact = gobjPoom.GetItemFromOid(lngOID) ' delete the contact objContact.Delete 

Again, you want to set the refresh so it happens. Next, the contact is retrieved with the public PocketOutlook.Application object variable reference gobjPoom. GetItemFromOid is called with the OID to return a single PocketOutlook item, whether it's a ContactItem, TaskItem, or AppointmentItem. This item is then deleted by calling the Delete method on the item itself.

The following code snippet demonstrates copying a contact:

 ' copying a contact, so let's make sure to refresh afterwards blnRefresh = True ' get the contact by its id Set objContact = gobjPoom.GetItemFromOid(lngOID) ' make a copy Set objContact = objContact.Copy() ' display it for editing objContact.Display 

Again, the refresh flag is set and the contact is retrieved from GetItemFromOid. With the valid contact object, the Copy method is called returning a new ContactItem object with the same values. A different variable could be used here, but it's not necessary because you are done with the original object. This newly created copy is then displayed for viewing and editing via Display on the object itself.

The following code segment is for a user choosing Show Appts For Contact. This simple call to frmAppt.ShowApptForContact passes the OID as the parameter. Although the Appointment form isn't published in this chapter, it's part of the sample application downloadable from www.samspublising.com (enter this book's ISBN in the Search field).

 ' show the appt form - giving it the OID (primary-key) frmAppt.ShowApptForContact (lngOID) 

In the following code, as with previous actions, set the refresh Boolean and retrieve the contact from the OID. Then the CompanyName value is stored to a local variable. Again, as before, we create a new ContactItem by calling the Add method of the Items collection. Because we have set values on the properties, we need to save the contact first by calling the Save method on the object. Then, the contact is displayed via the Display method.

 ' basically copying a contact, so let's make sure to refresh afterwards blnRefresh = True ' get the contact by its id Set objContact = gobjPoom.GetItemFromOid(lngOID) ' save the company name strCompanyName = objContact.CompanyName ' create a new contact and store back ' to the same object reference Set objContact = objFolderContact.Items.Add() ' set the company name objContact.CompanyName = strCompanyName ' save the contact before displaying, else error occurs objContact.Save ' display the contact objContact.Display 

The following snippet demonstrates how to beam a single contact via infrared. You can use this methodology to beam any POOM objects.

 ' get the contact by its id Set objContact = gobjPoom.GetItemFromOid(lngOID) ' create infrared folder Set objInfraredFolder = gobjPoom.GetDefaultFolder(olFolderInfrared) ' add the contact to the folder objInfraredFolder.AddItemToInfraredFolder olContactItem, objContact ' send the folder (contact) objInfraredFolder.SendToInfrared 

In this code, a ContactItem object is obtained via GetItemFromOid. Then, a reference is created to a folder used as a repository for the beaming. This folder appears the same as any of the other folders, but is obtained using the olFolderInfrared enumeration as the parameter for folder type to the GetDefaultFolder method.

After an infrared folder is created, items can be prepared for beaming by adding them to the folder. To add items to the infrared folder, use AddItemToInfraredFolder. The first parameter is the object type (olItemType); here, the enumeration olContactItem was used. The second parameter for the AddItemToInfraredFolder method is the object itselfobjContact. When all items are added to the folder, initiate the beaming by calling the SendToInfrared method.

Cleaning Up and Refreshing the Grid

After all actions are taken, the following code is what remains of ShowContextMenu. The menu resources are released using the DestroyMenu API with the menu handle hMenu. Then, if an action was taken that needs to refresh the contact grid, it's refreshed in place by using RefreshContacts without a seek value.

 ' release the menu resources DestroyMenu hMenu ' if we did something that requires, let's refresh If blnRefresh Then     ' refresh with no find     RefreshContacts "" End If 
The Form_Unload Routine

Listing 6.14 logs off POOM and cleans up the object by setting it to Nothing.

Listing 6.14 Logging Off and Cleaning Up
 Private Sub Form_Unload(Cancel As Integer)     ' logoff     gobjPoom.Logoff     ' clear reference to POOM     Set gobjPoom = Nothing End Sub 

Team-Fly    
Top
 


eMbedded Visual BasicR. WindowsR CE and Pocket PC Mobile Applications
eMbedded Visual BasicR. WindowsR CE and Pocket PC Mobile Applications
ISBN: N/A
EAN: N/A
Year: 2001
Pages: 108

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net