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. Figure 6.3. The Tasks Form can be sorted by any of the displayed fields. Figure 6.4. The Appointments Form provides a completely different view of appointments from the native PIM. 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: 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. 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 |