A Growing Sense of POOM

John Kennedy
Microsoft Corporation

August 7, 2001

I'm not feeling particularly mobile or witty, so I'll apologize in advance. The effects of a nasty cold and even nastier work deadlines have taken their toll on my sense of humor. The good news, of course, is that the Microsoft product I've been doing my part on is really going to "smoke" (as you Americans like to say), and will be shipping soon. And as I've been laid up in bed, I've had more time to devote to Pocket PC development and this column.

Before we start, it's only fair to say, "Okay, okay, I get the message." Enough of the fluff, we'll get down and dirty with some real eMbedded Visual Tools C++® application programming for the Pocket PC this month. In previous months, we've covered the preliminaries—how to get started, what tools you'll need and how to debug your programs—but we've slipped behind Larry and his eMbedded Visual Basic® quest to create useful code. So, here's the revenge—we're going to create a useful application.

Planning a Trip?

The application for this article is available in two formats. The TripPlannerExe.exe download is a ready-to-run program that anyone can use, even if you don't have a compiler. Launch this file, and three executables—one for each of the major Pocket PC CPUs—will be unpacked. Rename and copy the appropriate file to your device.

Download TripPlannerExe.Exe

The TripPlannerSource.exe download includes all the files that make up the eMbedded Visual Tools C++® Project, including commented source code and bitmaps.

Download TripPlannerSource.Exe

Back in the days when the Handheld PC was the Windows® CE device of choice, Microsoft released a slick program (or PowerToy) for planning business trips. Enter the relevant details, and the program creates appointments and reminders for you. Pretty neat. This application is currently lacking for the Pocket PC platform, which is as good a reason as any to write one.

As a result, during the course of this month's column you'll learn:

  • How to create a Wizard-style dialog based program
  • How to use graphics to brighten up your program
  • How to use text controls
  • How to use date and time picker controls
  • How to create calendar appointments programmatically

There's a heck of a lot of material to cover, so we better get started before I need another dose of hot lemon. You'll be delighted to learn that the final, ready-to-run application is available for downloading (it should run on all devices, including the Compaq iPAQ, Casio, and HP Jornada devices), as is the source code for your perusal. I strongly urge you to look through the source code, as there are a few little Pocket PC tricks in there. You'll also be able to adapt the program to suit you own needs by recompiling it on your own eMbedded Visual Tools C++ toolkit.

Using a Wizard to Create a Wizard

The design of this program is nothing revolutionary. It consists of several dialog boxes, each requesting information from the user. When the user taps the Next… button, the program displays the next dialog box in the sequence. After the last box has been displayed, the program collates the information it has gathered and uses it create two calendar appointments.

I decided to use a series of dialog boxes as stepping from one box to another makes it obvious to the user what they need to do. Further, on the Pocket PC platform, dialog boxes are always full-screen. This design decision by the Pocket PC team certainly simplifies the appearance and use of Pocket PC applications like this one.

If you are ever creating a program like this from scratch, it's a great idea to use one of the eMbedded Visual Tools C++ Wizards to create a skeleton program to get you going. This is exactly what I did, and in this case, I went whole hog and selected a "Hello World" Pocket PC application. This Wizard-created application provides all the code for managing a standard Windows message loop, and it even adds an About dialog box as a bonus. The About dialog code comes in very handy, and I cut and pasted it five times. The Control-V key is a great laborsaving device!

Step by Step

Before we go any further, here is how the program looks so you can get an idea of what I'm talking about. There are five different screen displays: one that appears when the program starts, and four dialogs that the user sees one after the other.

Figures 15. Different screens displayed by our exciting TripPlanner program

So how does the program work its way from dialog to dialog? We simply start a loop and use a variable that tells us which dialog to open. By looking at the feedback from the dialog routine, we can step to the next dialog in the sequence. We use the DialogBox function to make sure the dialog box is opened in "modal" format, which means it stays at the front of the display, locking out other operations and, in fact, can only be closed when the user clicks on the Cancel or OK buttons. More importantly, the DialogBox function won't return until the user clicks a button, so the loop won't waste time sucking up valuable CPU cycles doing nothing. Here's the code that does all that exciting stuff. By the way, the dialog boxes have been previously designed in the resource editor and are named IDD_STEP_1, IDD_STEP_2 and so on.

void DialogManager(HWND hWnd)
{

   do {

      switch (MyStep) {

      case 1:
         if (IDOK==DialogBox(hInst, (LPCTSTR)IDD_STEP_1, hWnd, (DLGPROC)Step1))
         {
            // Tapped on OK, move to next step
            MyStep=2;
         }
         else
         {
            // Tapped back, time to quit
            return;
            
         }
      break;

      case 2:
         if (IDOK==DialogBox(hInst, (LPCTSTR)IDD_STEP_2, hWnd, (DLGPROC)Step2))
         {
            // Tapped on OK, move to next step
            MyStep=3;
         }
         else
         {
            // Tapped back, go back one step
            MyStep=1;
         }
      break;

      case 3:
         if (IDOK==DialogBox(hInst, (LPCTSTR)IDD_STEP_3, hWnd, (DLGPROC)Step3))
         {
            // Tapped on OK, move to next step
            MyStep=4;
         }
         else
         {
            // Tapped back, go back one step
            MyStep=2;
         }
      break;

      case 4:
         if (IDOK==DialogBox(hInst, (LPCTSTR)IDD_STEP_4, hWnd, (DLGPROC)Step4))
         {
            // Clicked on OK, so return as this will
            // call function to create appointments
            // and quit.
            return;
         }
         else
         {
            // Tapped back, go back one step
            MyStep=3;
         }
      break;

      default:
            break;
      } // end of switch
   
   } while(true);
}

Graphics: Two Ways to Use Them

Most Pocket PC devices have nice color screens, and it's a shame not to make use of them by adding some pretty pictures to a program. I think this is especially true in the case of Pocket PC devices, where devices and software are more personal and stylish.

In this application, I used some graphics to brighten up the dialog boxes and to provide an opening splash screen. Nicole Wandschneider, a fellow Microsoftie, was kind enough to create these graphics especially for this project.

Adding a graphic to a dialog box is incredibly easy. You can add it using the dialog resource editor. Here are the steps involved:

  1. Design the graphic in a paint program, and keep the number of colors to 256 or less. The Pocket PC screen is 240 pixes wide by 320 pixels tall (give or take a few pixels for the menu and task bar), so size the graphics accordingly.
  2. Save the image to the same directory as your project as an 8-bit BMP file. You can save images in 24-bit format if you like, but when you load them into the embedded Toolkit, they won't be displayed in the resource editor.
  3. From the resource editor, right-click on the Bitmap node and select Import… from the pop-up menu. A file requestor will appear.
  4. Change the Files of Type option to *.* or you won't see any BMP files. Locate your file, and click Import. That's it.

Figure 6. Import your graphics into the resource editor. Stick to BMPs, GIFs and JPGs aren't supported.

Once you have imported all the graphics you need into the project, you can add them to the dialog box. The dialog box editor allows an image to be added as easily as any other control—simply drag the picture icon over and drop it on the dialog box in the approximate position. You can nudge it into place later with the cursor keys. The picture control starts off as a default square shape, but it will be resized to the shape of the imported graphic.

Figure 7. Add the Picture control to your dialog, and select the bitmap ID from the properties window.

You can test the control by typing Control-T, and you'll immediately notice several things. First, the background color of the dialog appears grey, and if you've assumed the background color was going to be white, you might be depressed. Secondly, the sizeing and positioning of the graphics are probably messed up. If you have spent time carefully aligning them, they'll be off by many pixels.

Figure 8. The test dialog display is often a little scrambled. Compare this with the program screen shot abovethey're the same, I promise.

Don't panic—just press escape to quit the test. For some reason the test version of the dialog doesn't create an accurate impression of the real thing. To get everything aligned properly, you need to look at the dialog in the emulator or a real device and then return to the resource editor to make changes. It can be a bit tedious, but you'll get there in the end.

Of course, most of the time when your program needs to display some graphics, you won't be able to do it this easily. In fact, most of the time, you'll have to go to the bother of declaring hDCs and hBitmaps and, well, here's an example that demonstrates what I'm talking about. This is the splash screen that appears when the program first starts. As with any good Windows program, we'll be doing the work in the part of the code that deals with the WM_PAINT message; the message that is sent to our program when the screen needs updating.

Again, the graphic we'll be displaying is loaded into the resource editor, but this time it's a full 240 pixels wide by 320 pixels high. It has an ID of IDB_BITMAP5 in the example that follows. Enough chatting, here's the code:

   case WM_PAINT:
      hdc = BeginPaint(hWnd, &ps);
   h_splash=LoadBitmap(hInst,MAKEINTRESOURCE(IDB_BITMAP5));
      hDC_splash=CreateCompatibleDC(hdc);
      SelectObject(hDC_splash,h_splash);
      BitBlt(hdc,0,0,240,320,hDC_splash,0,0,SRCCOPY);
      DeleteDC(hDC_splash);
      EndPaint(hWnd, &ps);
      break;

The BitBlt function does the actual drawing, copying data from hDC_splash pointing to the buffer we loaded the bitmap into, to hdc, which is the window display on the screen. BitBlt is a powerful function, so take the time to read up on it and explore some of its parameters.

We don't need to erase this image or make it conditional—the rest of the program is a series of dialog boxes, so they'll always appear in front of this image. Now let's return to those dialog boxes, and the controls that are hosted on them.

Controls: Text Boxes and Date-Time Controls

A dialog box is next to useless without some controls on it. The graphics are nice, but we need controls. Controls are the way in which the user sees and enters information into the program, and there is a wide range from which you can choose. The first type of control I used, and probably you will too, was a static text box. It's nothing more than a piece of text, so it's perfect for labelling the dialog and other controls. It's used to add a title and instructions to the dialog boxes.

The next simplest control is an edit box. In this application, an edit box is used in several places, for example, to allow the user to enter Departure and Return details. All controls work by responding to messages, so to use the edit box you must first send it a message telling it what text to display by default. Once the user closes the dialog box, you can then ask the control to send us its contents. Sound tricky? Not really, here's some code to demonstrate it.

// Send existing string to text box
SendDlgItemMessage(hDlg, IDC_EDIT_DETAILS, WM_SETTEXT, 0, (LPARAM)(LPCTSTR)szDescription);

// Retrieve string from the text box
SendDlgItemMessage(hDlg, IDC_EDIT_DETAILS, WM_GETTEXT, 255, (LPARAM)&szDescription);

In the code above, the hDlg parameter is the handle of the dialog box. The IDC_EDIT_DETAILS parameter is the ID of the edit box control. The WM_SETTEXT and WM_GETTEXT parameters are the messages. As you would expect, the first message sends the text to the control, the second retrieves it. The last parameter is the string where the text is stored. You can set the maximum size of the text to return to the string (255 in the example above) to make sure that nothing naughty happens to overwritten memory.

As in any Windows program, it's where you place these message-sending functions that's important. The text sending part above goes into the code that handles the WM_INITDIALOG message. This is the message that is sent when a dialog box is being created. The text retrieving part goes into the code that is called when the user clicks the OK button. Check out the sample code, and you'll see these functions occurring in each dialog box callback code function.

The remaining type of control used in this application is a Date Time Picker control. This is a control that makes it easy for the programmer (that's us) to add a method for users to select a date or a time. Programming a control like this from scratch would be a real nuisance, so it's a terrific time-saver to have one at your disposal. There's a magic spell needed to activate this control (and a few others) and it's in the sample code if you need it. The Picker control can display either the time, or the date, so we'll use one of each on each dialog.

Adding the control to the dialog is another drag-and-drop affair in the dialog editor, but once the controls are present, open their properties (highlight the control and press return) and under the styles tab, set one to Time and one to Short Date.

Figure 9. Setting the properties of a Date Time Picker control to select either time or datein this case time.

The next step is defining a SYSTEMTIME variable to store the date and time that the control will use. This is a structure that contains everything there is to know about a particular date and time, and in this application I use the same variable with both the time and the date control.

Again, you need to send the control a message to set or retrieve the current time. While the dialog is being initialized, the program sends the controls the current date and time, like this:

DateTime_SetSystemtime( GetDlgItem( Dlg, IDC_DEPARTURE_TIME), GDT_VALID, &outward_date);
DateTime_SetSystemtime( GetDlgItem( hDlg, IDC_DEPARTURE_DATE), GDT_VALID, &outward_date);

The IDs of the two controls in the code above are IDC_DEPARTURE_TIME and IDC_DEPARTURE_DATE, and the DateTime_SetSystemtime function is used to send the message.

There is a slight complication when it comes to getting our hands on the values the user has entered. While, as usual, the program waits until the user clicks the OK button to extract the time and date from the controls, this could potentially give the wrong answer. Each control has its own idea as to the time and date, and while we initialized each to the same value to start with, changing either means there are two different time/dates to contend with. This means that when it comes to extracting the data, we'll have no idea which one is correct.

We can deal with this by looking for another message. When the user makes a change to either the time or the date control, our program will know because it receives a DTN_DATETIMECHANGE message. We can therefore update both controls to the same time/date whenever either control is used. When the user clicks the OK button, we can therefore read the time/date from either of the controls as they both contain the same value. Here's the code that does that:

case WM_NOTIFY:

switch ((UINT)wParam)
   {
      case IDC_DEPARTURE_DATE: 
      if (LPNMHDR(lParam)->code==DTN_DATETIMECHANGE)
      {
         // Get value from DATE, copy it to TIME
   DateTime_GetSystemtime(GetDlgItem(hDlg,IDC_DEPARTURE_DATE),&outward_date);
DateTime_SetSystemtime(GetDlgItem(hDlg,IDC_DEPARTURE_TIME),GDT_VALID,&outward_date);
      }
               
      break;

      case IDC_DEPARTURE_TIME:
      if (LPNMHDR(lParam)->code==DTN_DATETIMECHANGE)
      {
         // Get value from TIME, copy it to DATE
            DateTime_GetSystemtime(GetDlgItem(hDlg,IDC_DEPARTURE_TIME),&outward_date);
DateTime_SetSystemtime(GetDlgItem(hDlg,IDC_DEPARTURE_DATE),GDT_VALID,&outward_date);
      }
      break;
   }
break;

A Sense of POOM

That's most of the grunt work in this application—getting input from the user using text, and Date Time Picker controls. When the final dialog is reached, the program has collected two SYSTEMTIME structures, and three text strings that describe them.

Armed with this information, it's possible to do something clever like creating an appointment. Remember, we're dealing with a Pocket PC here, not a standard desktop computer. The entire point of a Pocket PC is that you carry it with you, and count on its alarms, reminders, contacts, and tasks databases. Once you realize that, you can write some genuinely useful applications. In this case, the program will create appointments for the Departing and Returning flights in the Pocket PC's built-in calendar application.

I'm not going to lie and say that accessing the internal PIM features of a Pocket PC is easy, peasy, lemon squeezie. It's not. To understand it totally requires a good knowledge of C++ and COM, and that's something that you can't get in an afternoon of tinkering (and believe me, I've tried). However, the good news is that there is help out there in the form of a special SDK called "POOM," the Pocket Outlook Object Model.

This download consists of a very useful help file, and some code to include in your program. You'll also need to link with some particular libraries (oleaut32.lib, ole32.lib) for your program to work properly.

If there is interest in looking at POOM at more detail, then I'd be happy to expand on it (you're welcome to leave a comment or e-mail me at johnkenn@microsoft.com), but in this case, I'm happy to leave you the code as is, with pointers to other sources of POOM help.

The code in this application consists of one initialization function, and the function that creates the appointment. It looks scary, so have a good read through the documentation that comes with the POOM SDK. The article included in the download is better than all the Windows CE reference books I've seen so far, so for the moment it's the best reference available. You may also want to have a look at https://support.microsoft.com/support/kb/articles/Q265/7/71.ASP.

That's All for Now

As I mentioned at the start, the source code and the ready-to-run version are available for downloading. It's definitely worth your while to have a look through the code, as there are many more comments (and maybe some jokes) than I've had time or space to put in this column. I hope you'll find this application useful, and feel free to leave a comment as to what you think.See you next time, when I'll be back on my feet (and motorbike), and we'll look at some more Pocket PC programming.

 

John Kennedy is a Technical Writer/Programmer in the Visual C++ group by day and leads a secret life as a Pocket PC developer by night.

Larry Roof is a partner at tonked, a firm that specializes in the development of mobile solutions and training. He's the author of Professional Visual Basic Windows CE, available from Wrox Press.