Embedded Visual Basic Windows Ce And Pocket Pc Mobile Applications [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Embedded Visual Basic Windows Ce And Pocket Pc Mobile Applications [Electronic resources] - نسخه متنی

Chris Tacke; Timothy Bassett

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید



eMbedded Visual Basic®: Windows® CE and Pocket PC Mobile Applications

By
Chris Tacke, Timothy Bassett

Table of ContentsChapter 6.
The Pocket Outlook Object Model: Accessing Contacts and Calendar Items


Creating the Pocket PIM Sample Application


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:

  • 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.


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 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



    / 108