MobileMoving Dialogs from the Desktop to Windows CE

Moving Dialogs from the Desktop to Windows CE

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Dialogs are a less superficial porting issue than most of the other items you find in the resource file, for a couple of reasons. First, dialogs invariably include a lot of text items, all of which must be handled as Unicode strings under CE. Second, many desktop application dialogs will be too large to display properly on a CE-size screen. We’ve looked a bit at handling Unicode strings, and we’ll expand that discussion in the articles ahead. Now let’s look at what to do with dialogs that are too big for the Windows CE screen.

Here’s the simplest strategy for porting dialogs:

  • Examine the resource file and find dialogs that are too big to display on CE screens.
  • Subdivide their controls into groups based on function and redesign the groups as pages of a tabbed dialog.

We are going to do just exactly this. For purposes of illustration, we’ll port the user interface of the dialog-based application pictured in Figure 1.

Figure 1: DataDlg is a Win32 application that uses this dialog box as its main user interface.

Win 32 Source Files for the DataDlg Example

All we really need to observe about the code for the UFO dialog is that it’s modeless, so we can’t use EndDialog() to dismiss it and that we’ve conveniently omitted all the code that processes and stores dialog input. From the screenshot in Figure 1, we can easily determine that this dialog is much too large to display on a CE screen. You may not have the luxury of screen shots of all your application’s dialogs, so a good place to look to assess the dimensions of a dialog that’s a candidate for porting is (you knew it) the resource file.

Here’s an excerpt from DataDlg.rc that gives the dimensions of the UFO dialog.

UFODLGBOX DIALOGEX 0, 0, 422, 237
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION
CAPTION "UFO Data Management Form X"
FONT 8, "MS Sans Serif"
BEGIN
    LTEXT           "&Name",IDC_STATIC,5,5,20,8
    EDITTEXT        IDC_NAME,35,5,70,15,ES_AUTOHSCROLL
    LTEXT           "&Badge Number",IDC_STATIC,5,30,48,8
    EDITTEXT        IDC_BADGE,65,30,40,15,ES_AUTOHSCROLL
    LTEXT           "&Description of Encounter",
                        IDC_STATIC,5,55,90,8
    EDITTEXT        IDC_DESC,30,65,75,40,ES_MULTILINE |
                                         ES_AUTOVSCROLL
    GROUPBOX        "&Category",IDC_STATIC,25,115,80,50
    CONTROL         "Sighting",IDC_CAT,"Button",
                               BS_AUTORADIOBUTTON |
                               WS_GROUP |
                               WS_TABSTOP,30,130,65,10
    CONTROL         "Contact",IDC_RADIO2,"Button",
                               BS_AUTORADIOBUTTON |
                               WS_TABSTOP,30,145,70,10
    GROUPBOX        "&Mode of Travel",IDC_STATIC,25,165,80,65,
                               WS_GROUP
    CONTROL         "Space Ship",IDC_SPACESHIP,"Button",
                               BS_AUTOCHECKBOX |
                               WS_TABSTOP,30,180,70,10
    CONTROL         "Levitation",IDC_LEVITATION,"Button",
                               BS_AUTOCHECKBOX |
                               WS_TABSTOP,30,195,65,10
    CONTROL         "Bicycle",IDC_BICYCLE,"Button",
                               BS_AUTOCHECKBOX |
                               WS_TABSTOP,30,210,70,10
    LTEXT                "Where Seen (simple combo)",
                               IDC_STATIC,140,5,90,8
    COMBOBOX        IDC_WHERE_SEEN,140,20,90,45,CBS_SIMPLE |
                                                CBS_SORT |
                               WS_VSCROLL | WS_TABSTOP
    LTEXT                "&Physical Evidence",
                               IDC_STATIC,145,75,59,8
    LTEXT                "(dropdown combo)",IDC_STATIC,145,85,65,8
    COMBOBOX        IDC_PHYS_EVIDENCE,140,100,85,50,
                                      CBS_DROPDOWN | CBS_SORT |
                                      WS_VSCROLL | WS_TABSTOP
    SCROLLBAR       IDC_FRIENDLY,120,145,105,11
    LTEXT                 "Friendliness",IDC_STATIC,160,160,38,8
    SCROLLBAR       IDC_TALKATIVE,120,185,105,11
    LTEXT                 "Willingness to Communicate",
                               IDC_STATIC,130,200,89,8
    LTEXT                "&Home Office (list box)",
                               IDC_STATIC,255,5,68,8,0,0,
                               HIDC_STATIC
    LISTBOX         IDC_OFFICE,245,20,85,40,LBS_SORT |
                               LBS_NOINTEGRALHEIGHT |
                               WS_VSCROLL | WS_TABSTOP
    LTEXT           "&Abductions",IDC_STATIC,297,75,36,8
    LTEXT           "droplist combo",IDC_STATIC,290,87,50,8
    COMBOBOX  IDC_ABDUCTIONS,245,99,140,50,CBS_DROPDOWNLIST |
                               CBS_SORT | WS_VSCROLL | WS_TABSTOP
    GROUPBOX    "Appearance - Check All That Apply",
                               IDC_STATIC,250,130,150,105
    CONTROL         "Scary",IDC_SCARY,"Button",
                               BS_AUTOCHECKBOX |
                               WS_TABSTOP,265,150,34,10
    CONTROL         "Cute",IDC_CUTE,"Button",BS_AUTOCHECKBOX |
                               WS_TABSTOP, 265,165,31,10
    CONTROL         "Dressed like Leonard Nemoy",
                               IDC_SPOCK,"Button",
                               BS_AUTOCHECKBOX |
                               WS_TABSTOP,265,180,107,10
    CONTROL         "Not Dressed",IDC_THINKER,"Button",
                               BS_AUTOCHECKBOX |
                               WS_TABSTOP,265,195,55,10
    CONTROL         "Can't Recall",IDC_WHOOPS,"Button",
                               BS_AUTOCHECKBOX |
                               WS_TABSTOP,265,210,54,10
    PUSHBUTTON      "OK",IDOK,345,5,70,14
    PUSHBUTTON      "Cancel",IDCANCEL,345,30,70,14
    PUSHBUTTON      "&Clear Form",IDC_CLEARCTRLS,345,55,70,14,
                               BS_NOTIFY
END

Here’s what we learn from this excerpt. First, the dialog size is 422 x 237 screen units. It might fit on the screen of a PC Pro Windows CE device, but on anything else it would be too large. Windows CE supports the same set of predefined controls as Win 32 (buttons, listboxes, and so forth), so we really only have one concern about the individual controls at this point—their size. We’ll make the edit controls smaller, but because they can be set to scroll both horizontally and vertically, shrinking them has no impact on their functionality.

Using Windows CE Tabbed Dialogs

The easiest and most obvious way to restructure the UFO Dialog is to subdivide its functional groups of controls and organize them as the pages of a tabbed dialog. This style of dialog is called a Property Sheet. It has a lot of advantages. Porting is quick and requires little change in the dialog’s message handling logic. You can reuse a lot of the resource and header files from the original application. The existing message processing logic must be subdivided into functions that handle each of the pages, but in most other ways, dialog handling code goes “straight across.” And finally, existing help files and user documentation won’t need drastic modification because the application works in the same general way as before.

If you are familiar with the format of the text-based resource file, you can save a lot of time by copying strings and manifest constants from the Win32 application into the CE-side files. If this approach has no appeal, here’s how to use the Visual C++ resource editor tools to copy controls from one dialog to another.

  • In the Resource Editor, open the dialog template from which you want to copy controls or strings.
  • Select what you want to copy by clicking on it. An outline box with “handles” will appear around it at the corners and midpoints of the enclosing rectangle.
  • Press Ctrl+C or use Edit, Copy from the pull-down menu.
  • Again in the resource editor, open or insert the dialog you want to paste the control into. (Right-click over the Dialog folder of the Resource workspace tab to open the menu that lets you insert a new dialog.)
  • Use Ctrl+V or Edit, Paste to copy the control to the new dialog template.

In this fashion, you can work between the Win32 dialog and the Property Sheet page templates using the Clipboard to copy and paste controls and static text. Although this approach can become a bit tedious, it copies the ID’s of the controls along with their size and appearance. This is a time saver when it’s time to connect the controls to their processing logic because there is less chance of making errors such as assigning different symbolic constants the same value.

From a user’s point of view, a property sheet behaves like a dialog with tabbed pages. When they tap on a tab, the corresponding page is displayed. From a programmer’s point of view, it works a little differently. The individual pages are treated as discreet dialogs, each of which has its own dialog procedure. The “tabs” across the top of the property sheet behave as another control, one that sends your application WM_NOTIFY messages. These messages tell when a page is losing or gaining focus, and give the individual dialog procedures a chance to acquire and validate input. A dialog page that is about to lose the focus also has the opportunity to prevent this from happening. This is useful if you want to require a given page’s controls be completely filled in or when you can only accept validated control input.

Here’s the source code for the tabbed dialog. Header and resource files are omitted here, but can be found in the download file at the end of this article.

// TabbedDlg.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "TabbedDlg.h"
#include "resource.h"
#include <commctrl.h>
#include <commdlg.h>                 // Common dialog box includes
#include <prsht.h>                   // Property sheet includes

#define MAX_LOADSTRING 100
#define dim(x) (sizeof(x) / sizeof(x[0]))

// Global Variables:
HINSTANCE                  hInst;    // The current instance
HWND                       hwndCB;   // The command bar handle

// Foward declarations of functions included in this code module:
ATOM MyRegisterClass (HINSTANCE hInstance, LPTSTR szWindowClass);
BOOL InitInstance (HINSTANCE, int);

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK About   (HWND, UINT, WPARAM, LPARAM);

BOOL CreatePropertySheet(HWND);

//Property Sheet Dialog Procs
BOOL CALLBACK AgentDlgProc (HWND hDlg, UINT message,
                            WPARAM wParam, LPARAM lParam);
BOOL CALLBACK EncounterDlgProc (HWND hDlg, UINT message,
                                WPARAM wParam, LPARAM lParam);
BOOL CALLBACK ActionsDlgProc (HWND hDlg, UINT message,
                              WPARAM wParam, LPARAM lParam);
BOOL CALLBACK AppearanceDlgProc (HWND hDlg,
                                 UINT message, WPARAM wParam,
                                 LPARAM lParam);
BOOL CALLBACK SceneDlgProc (HWND hDlg, UINT message,
                            WPARAM wParam, LPARAM lParam);
BOOL CALLBACK DescriptionDlgProc(HWND hDlg, UINT message,
                                 WPARAM wParam, LPARAM lParam);
void InitComboCtrls (HWND, int, int );

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPTSTR    lpCmdLine,
                   int       nCmdShow)
{
      MSG msg;
      HACCEL hAccelTable;

      // Perform application initialization:
      if (!InitInstance (hInstance, nCmdShow))
      {
            return FALSE;
      }

      hAccelTable = LoadAccelerators(hInstance,
                                    (LPCTSTR)IDC_TABBEDDLG);

      // Main message loop:
      while (GetMessage(<msg, NULL, 0, 0))
      {
            if (!TranslateAccelerator(msg.hwnd, hAccelTable, <msg))
            {
                  TranslateMessage(<msg);
                  DispatchMessage(<msg);
            }
      }

      return msg.wParam;
}

//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
//  COMMENTS:
//
//    This function and its usage is only necessary if you want
//    this codeto be compatible with Win32 systems prior to the
//    'RegisterClassEx' function that was added to Windows 95.
//    It is important to call this function so that the
//    application will get 'well formed' small icons associated
//    with it.
//
ATOM MyRegisterClass(HINSTANCE hInstance, LPTSTR szWindowClass)
{
      WNDCLASS      wc;

    wc.style          = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc    = (WNDPROC) WndProc;
    wc.cbClsExtra     = 0;
    wc.cbWndExtra     = 0;
    wc.hInstance      = hInstance;
    wc.hIcon          = LoadIcon(hInstance,
                        MAKEINTRESOURCE(IDI_TABBEDDLG));
    wc.hCursor        = 0;
    wc.hbrBackground  = (HBRUSH) GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName   = 0;    //don't load a menu; we'll use a
                              //command bar
    wc.lpszClassName  = szWindowClass;

      return RegisterClass(&wc);
}

//
//  FUNCTION: InitInstance(HANDLE, int)
//
//  PURPOSE: Saves instance handle and creates main window
//
//  COMMENTS:
//
//    In this function, we save the instance handle in a global
//    variable and create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
      HWND      hWnd;
      TCHAR     szTitle[MAX_LOADSTRING];
                // The title bar text
      TCHAR     szWindowClass[MAX_LOADSTRING];
                // The window class name

// Store instance handle in our global variable
      hInst = hInstance;
      // Initialize global strings
      LoadString(hInstance, IDC_TABBEDDLG, szWindowClass,
                             MAX_LOADSTRING);
      MyRegisterClass(hInstance, szWindowClass);

      LoadString(hInstance, IDS_APP_TITLE, szTitle,
                            MAX_LOADSTRING);
      hWnd = CreateWindow(szWindowClass, szTitle, WS_VISIBLE,
                          0, 0,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL);

//fail and bail if we didn't get a window
      if (!hWnd)
      {
            return FALSE;
      }

      ShowWindow(hWnd, nCmdShow);
      UpdateWindow(hWnd);
      if (hwndCB)
            CommandBar_Show(hwndCB, TRUE);

      return TRUE;
}

//
//  FUNCTION: WndProc(HWND, unsigned, WORD, LONG)
//
//  PURPOSE:  Processes messages for the main window.
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
                         WPARAM wParam, LPARAM lParam)
{
      int wmId, wmEvent;

      switch (message)
      {
            case WM_COMMAND:
                  wmId    = LOWORD(wParam);
                  wmEvent = HIWORD(wParam);
                  // Parse the menu selections:
                  switch (wmId)
                  {
                        case IDM_SHOW_TABBED_DLG:
                           CreatePropertySheet(hWnd);
                           break;
                        case IDM_HELP_ABOUT:
                           DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX,
                                             hWnd, (DLGPROC)About);
                           break;
                        case IDM_FILE_EXIT:
                           DestroyWindow(hWnd);
                           break;
                        default:
                           return DefWindowProc(hWnd, message,
                                                wParam, lParam);
                  }
                  break;
            case WM_CREATE:
                  //create and show a command bar menu
                  hwndCB = CommandBar_Create(hInst, hWnd, 1);
                  CommandBar_InsertMenubar(hwndCB, hInst,
                                           IDM_MENU, 0);
                  CommandBar_AddAdornments(hwndCB, 0, 0);
                  break;
            case WM_DESTROY:
                  CommandBar_Destroy(hwndCB);
                  PostQuitMessage(0);
                  break;
            default:
                  return DefWindowProc(hWnd, message, wParam,
                                                      lParam);
   }
   return 0;
}

// Mesage handler for the About box.
LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam,
                                                LPARAM lParam)
{
      RECT rt, rt1;
      int DlgWidth, DlgHeight;      // dialog width and height
                                    // in pixel units
      int NewPosX, NewPosY;

      switch (message)
      {
            case WM_INITDIALOG:
                  // trying to center the About dialog
                  if (GetWindowRect(hDlg, &rt1)) {
                        GetClientRect(GetParent(hDlg), &rt);
                        DlgWidth    = rt1.right  - rt1.left;
                        DlgHeight   = rt1.bottom - rt1.top ;
                        NewPosX     = (rt.right  - rt.left
                                                 - DlgWidth)/2;
                        NewPosY     = (rt.bottom - rt.top
                                                 - DlgHeight)/2;

                        // if the About box is larger than the
                        // physical screen
                        if (NewPosX < 0) NewPosX = 0;
                        if (NewPosY < 0) NewPosY = 0;
                        SetWindowPos(hDlg, 0, NewPosX, NewPosY,
                                           0, 0, SWP_NOZORDER |
                                                 SWP_NOSIZE);
                  }
                  return TRUE;

            case WM_COMMAND:
                  if ((LOWORD(wParam) == IDOK) ||
                      (LOWORD(wParam) == IDCANCEL))
                  {
                        EndDialog(hDlg, LOWORD(wParam));
                        return TRUE;
                  }
                  break;
      }
    return FALSE;
}

//----------------------------------------------------------------
// DoMainCommandShowProp - Process show property sheet command.
//
BOOL CreatePropertySheet(HWND hWnd)
  {

    PROPSHEETPAGE pspPropPage[6];
    PROPSHEETHEADER pshPropSheet;
    int i;

    // Zero all the property page structures.
    memset (&pspPropPage, 0, sizeof (pspPropPage));
    // Fill in default values in property page structures.
    for (i = 0; i < dim(pspPropPage); i++)
      {
        pspPropPage[i].dwSize    = sizeof (PROPSHEETPAGE);
        pspPropPage[i].dwFlags   = PSP_DEFAULT;
        pspPropPage[i].hInstance = hInst;
        pspPropPage[i].lParam    = (LPARAM)hWnd;
    }
    // Set the dialog box templates for each page.
    pspPropPage[0].pszTemplate = MAKEINTRESOURCE
                                 (IDD_AGENT_DIALOG);
    pspPropPage[1].pszTemplate = MAKEINTRESOURCE
                                 (IDD_ENCOUNTER_DIALOG);
    pspPropPage[2].pszTemplate = MAKEINTRESOURCE 
                                 (IDD_SCENE_DIALOG);
    pspPropPage[3].pszTemplate = MAKEINTRESOURCE
                                 (IDD_ACTIONS_DIALOG);
    pspPropPage[4].pszTemplate = MAKEINTRESOURCE
                                 (IDD_APPEARANCE_DIALOG);
    pspPropPage[5].pszTemplate = MAKEINTRESOURCE
                                 (IDD_DESCRIPTION_DIALOG);

    // Set the dialog box procedures for each page.
    pspPropPage[0].pfnDlgProc   = AgentDlgProc;
    pspPropPage[1].pfnDlgProc   = EncounterDlgProc;
    pspPropPage[2].pfnDlgProc   = SceneDlgProc;
    pspPropPage[3].pfnDlgProc   = ActionsDlgProc;
    pspPropPage[4].pfnDlgProc   = AppearanceDlgProc;
    pspPropPage[5].pfnDlgProc   = DescriptionDlgProc;

    // Initialize property sheet structure.
    pshPropSheet.dwSize      = sizeof (PROPSHEETHEADER);
    pshPropSheet.dwFlags     = PSH_PROPSHEETPAGE;
    pshPropSheet.hwndParent  = hWnd;
    pshPropSheet.hInstance   = hInst;
    pshPropSheet.pszCaption  = TEXT ("UFO Dialog");
    pshPropSheet.nPages      = dim(pspPropPage);
    pshPropSheet.nStartPage  = 0;
    pshPropSheet.ppsp        = pspPropPage;
    pshPropSheet.pfnCallback = 0;

    // Create and display property sheet.
    PropertySheet (&pshPropSheet);
    return 0;
}

BOOL CALLBACK AgentDlgProc(HWND hDlg, UINT message, WPARAM wParam,
                                                    LPARAM lParam)
{
      HWND hWndCtl;
      switch (message)
      {
            case WM_INITDIALOG:
                  //init the Home Office Combo
                  hWndCtl = GetDlgItem( hDlg, IDC_OFFICE );
                  InitComboCtrls(  hWndCtl, IDS_OFFICE1, 3 );
                  return TRUE;

            case default:
                  break;
      }
    return FALSE;
}

BOOL CALLBACK EncounterDlgProc(HWND hDlg, UINT message,
                               WPARAM wParam, LPARAM lParam)
{
      HWND hWndCtl;
      switch (message)
      {
            case WM_INITDIALOG:
                  //init the Category Combo
                  hWndCtl = GetDlgItem( hDlg, IDC_CAT );
                  InitComboCtrls(  hWndCtl, IDS_CAT1, 2 );

                  //init the Mode of Travel Combo
                  hWndCtl = GetDlgItem( hDlg, IDC_TRAVEL );
                  InitComboCtrls(  hWndCtl, IDS_TRAVEL1, 3 );
                  return TRUE;

            case default:
                  break;
      }
    return FALSE;
}

BOOL CALLBACK ActionsDlgProc(HWND hDlg, UINT message,
                             WPARAM wParam, LPARAM lParam)
{
      HWND hWndCtrl;
      long lCurrScrollPos;
      switch (message)
      {
            case WM_INITDIALOG:
                  //init the Abductions Combo
                  hWndCtrl = GetDlgItem( hDlg, IDC_ABDUCTIONS );
                  InitComboCtrls(  hWndCtrl, IDS_ABDUCTIONS1, 3 );
                  //set ranges for the scoll bars
                  hWndCtrl = GetDlgItem( hDlg, IDC_FRIENDLY );
                  SetScrollRange( hWndCtrl, SB_CTL, 1, 5, TRUE );
                  SetScrollPos( hWndCtrl, SB_CTL, 1, TRUE );
                  //store the current scroll bar pos in the ctrl's
                  //window's user data bytes
                  SetWindowLong( hWndCtrl, GWL_USERDATA, 1 );

                  //now do the same init for the 2nd scroll bar
                  hWndCtrl = GetDlgItem( hDlg, IDC_TALKATIVE );
                  SetScrollRange( hWndCtrl, SB_CTL, 1, 5, TRUE );
                  SetScrollPos( hWndCtrl, SB_CTL, 1, TRUE );
                  SetWindowLong( hWndCtrl, GWL_USERDATA, 1 );
                  return TRUE;

            case WM_HSCROLL:
                  //lparam is the handle to the scollbar ctrl
                  //window
                  hWndCtrl = (HWND) lParam;
                  //we store the current scroll pos in the window's
                  //user data
                  //words -- under ce this buffer size must be a
                  //multiple of 4 bytes
                  lCurrScrollPos = GetWindowLong( hWndCtrl,
                                                  GWL_USERDATA );

                  switch( LOWORD( wParam ) )
                  {
                        //now recalculate the scroll bar position
                        //and redraw
                        case SB_LINELEFT:
                        case SB_PAGELEFT:
                              lCurrScrollPos = ( lCurrScrollPos
                                                 - 1 > 0 ) ?
                                    lCurrScrollPos - 1 : 1 ;
                              SetScrollPos(hWndCtrl, SB_CTL,
                                    (int)lCurrScrollPos, TRUE );
                              break;

                        case SB_LEFT:
                              lCurrScrollPos = 1;
                              SetScrollPos(hWndCtrl, SB_CTL,
                                    (int)lCurrScrollPos, TRUE );
                              break;

                        case SB_RIGHT:
                              lCurrScrollPos = 5;
                              SetScrollPos(hWndCtrl, SB_CTL,
                                    (int)lCurrScrollPos, TRUE );
                              break;


                        case SB_LINERIGHT:
                        case SB_PAGERIGHT:
                              lCurrScrollPos =
                              ( lCurrScrollPos + 1 < 6 ) ?
                                lCurrScrollPos + 1 : 5 ;
                              SetScrollPos(hWndCtrl, SB_CTL,
                                    (int)lCurrScrollPos, TRUE );
                              break;
                        default:
                              break;
                  } //end scroll bar message switch
                    //write the new scroll pos value to the window
                    //bytes
                  SetWindowLong( hWndCtrl, GWL_USERDATA,
                                 lCurrScrollPos );
                  break;

            case default:
                  break;
      
            default:
                  break;
      }
    return FALSE;
}

BOOL CALLBACK AppearanceDlgProc(HWND hDlg, UINT message,
                                WPARAM wParam, LPARAM lParam)
{

    return FALSE;
}

BOOL CALLBACK SceneDlgProc(HWND hDlg, UINT message,
                           WPARAM wParam, LPARAM lParam)
{
      HWND hWndCtl;

      switch (message)
      {
            case WM_INITDIALOG:
                  //init the Category Combo
                  hWndCtl = GetDlgItem( hDlg, IDC_WHERE_SEEN );
                  InitComboCtrls(  hWndCtl, IDS_WHERE1, 3 );

                  //init the Mode of Travel Combo
                  hWndCtl = GetDlgItem( hDlg, IDC_PHYS_EVIDENCE );
                  InitComboCtrls( hWndCtl, IDS_PHYS_EVIDENCE1, 3 );
                  return TRUE;

            case default:
                  break;
      }
    return FALSE;
}

BOOL CALLBACK DescriptionDlgProc(HWND hDlg, UINT message,
                                 WPARAM wParam, LPARAM lParam)
{
    return FALSE;

}

//init the combos
void InitComboCtrls( HWND hwndCtrl, int iBaseStringId,
                                    int iNumberStrings 
{
      TCHAR szBuffer[48];
      int i;
      LONG lOk;

      for(i = 0; i < iNumberStrings; i++, iBaseStringId++ )
      {
            LoadString( hInst, iBaseStringId, szBuffer,
                                              sizeof(szBuffer) );
            //add the string to the end of the list
            lOk = SendMessage( hwndCtrl, CB_INSERTSTRING,
                         -1, (LPARAM)(LPSTR)szBuffer );
      }//end for i...
      
      lOk = SendMessage( hwndCtrl, CB_SETCURSEL,0, 0 );

}

Setting Up a Property Sheet

In the TabbedDlg example, we launch the property sheet in response to a drop-down menu choice:

             case IDM_SHOW_TABBED_DLG:
                  CreatePropertySheet(hWnd);
                  break;

After we split the UFO dialog into a set of smaller templates, there are three more things we have to do to turn them into a property sheet: initialize an array of property sheet structures, call the PropertySheet() function to create the control, and subdivide the original dialog message handling code into units that match the controls in each of the new dialog templates for the property pages.

Here’s the declaration of the PROPSHEETPAGE structure, more or less as found in the Windows CE prsht.h header file. (I eliminated some of the conditionals to make it more readable and added the comments about the uses of the members.)

typedef struct _PROPSHEETPAGE {
        DWORD           dwSize;      //size of this structure
        DWORD           dwFlags;     //flags set behaviors and
                                     //indicate which fields are
                                     //valid
        HINSTANCE   hInstance;       //instance handle of this app
        union 
        {
            LPCSTR          pszTemplate;
            //name of dialog template for page
            LPCDLGTEMPLATE  pResource;
            //pointer to a memory based template
         };
        union
        {
            HICON       hIcon;      //must be set to NULL
            LPCSTR      pszIcon;    //must be set to NULL
         };
            LPCSTR      pszTitle;   //null terminated title string
                                    //if present, this overrides
                                    //template
            DLGPROC     pfnDlgProc; //ptr to the DlgProc for the
                                    //page
                                    //DlgProc MUST NOT call
                                    //EndDialog()
            LPARAM      lParam;     //optional user data passed in
                                    //lParam with WM_INITDIALOG
            LPFNPSPCALLBACKA
                        pfnCallback; //optional callback fxn ptr.
                                    //If present, the call back fxn
                                    //gets called when page is
                                    //created and when it's about
                                    //to be destroyed
            UINT FAR * pcRefParent; //optional ptr to reference
                                    //count. Ignored if style flags
                                    //don't contain
                                    //PSP_USERREFPARENT
} PROPSHEETPAGE;

As you can see from the structure declaration, the PROPSHEETPAGE effectively defines the appearance and behavior of a page, tying it to a dialog procedure and a dialog template. Here’s how we set up a group of pages for the UFO Property Sheet:

BOOL CreatePropertySheet(HWND hWnd)
  {

    PROPSHEETPAGE pspPropPage[6];
    PROPSHEETHEADER pshPropSheet;
    int i;

    // Zero all the property page structures.
    memset (&pspPropPage, 0x0, sizeof (pspPropPage));

Here, we declare an array of PROPSHEETPAGE structures, and then, before using them, we initialize the array with NULLs by calling memset(). I have found that even when the documentation says otherwise, it’s a good practice to initialize structures in this way, and set the size member if there is one.

    // Fill in default values in property page structures.
    for (i = 0; i < dim(pspPropPage); i++)
     {
        pspPropPage[i].dwSize    = sizeof (PROPSHEETPAGE);
        pspPropPage[i].dwFlags   = PSP_DEFAULT;
        pspPropPage[i].hInstance = hInst;
        pspPropPage[i].lParam    = (LPARAM)hWnd;
    }

Next, we loop through the array and set the members that are the same for all of the structures.

    // Set the dialog box templates for each page.
    pspPropPage[0].pszTemplate = MAKEINTRESOURCE
                                 (IDD_AGENT_DIALOG);
    pspPropPage[1].pszTemplate = MAKEINTRESOURCE
                                 (IDD_ENCOUNTER_DIALOG);
    pspPropPage[2].pszTemplate = MAKEINTRESOURCE
                                 (IDD_SCENE_DIALOG);
    pspPropPage[3].pszTemplate = MAKEINTRESOURCE
                                 (IDD_ACTIONS_DIALOG);
    pspPropPage[4].pszTemplate = MAKEINTRESOURCE
                                 (IDD_APPEARANCE_DIALOG);
    pspPropPage[5].pszTemplate = MAKEINTRESOURCE
                                 (IDD_DESCRIPTION_DIALOG);

Now, we’ve associated the dialog template for each of the pages, identifying the templates their manifest constants.

    // Set the dialog box procedures for each page.
    pspPropPage[0].pfnDlgProc = AgentDlgProc;
    pspPropPage[1].pfnDlgProc = EncounterDlgProc;
    pspPropPage[2].pfnDlgProc = SceneDlgProc;
    pspPropPage[3].pfnDlgProc = ActionsDlgProc;
    pspPropPage[4].pfnDlgProc = AppearanceDlgProc;
    pspPropPage[5].pfnDlgProc = DescriptionDlgProc;

Finally, we tie each of the property pages to the dialog procedure that processes its control messages.

We’ve defined a group of pages for the property sheet. Now, all that’s left to do is initialize the structure that collects the pages in a property sheet control.

    // Initialize property sheet structure.
    pshPropSheet.dwSize      = sizeof (PROPSHEETHEADER);
    pshPropSheet.dwFlags     = PSH_PROPSHEETPAGE;
    pshPropSheet.hwndParent  = hWnd;
    pshPropSheet.hInstance   = hInst;
    pshPropSheet.pszCaption  = TEXT ("UFO Dialog");
    pshPropSheet.nPages      = dim(pspPropPage);
    pshPropSheet.nStartPage  = 0;
    pshPropSheet.ppsp        = pspPropPage;
    pshPropSheet.pfnCallback = 0;

Finally, we create the control:

    // Create and display property sheet.
PropertySheet (&pshPropSheet);
    return 0;
}

Here is the code for one of the dialog procedures that handles the EncounterDlg property page:

BOOL CALLBACK EncounterDlgProc(HWND hDlg, UINT message,
                               WPARAM wParam, LPARAM lParam)
{
      HWND hWndCtl;
      switch (message)
      {
            case WM_INITDIALOG:
                  //init the Category Combo
                  hWndCtl = GetDlgItem( hDlg, IDC_CAT );
                  InitComboCtrls(  hWndCtl, IDS_CAT1, 2 );

                  //init the Mode of Travel Combo
                  hWndCtl = GetDlgItem( hDlg, IDC_TRAVEL );
                  InitComboCtrls(  hWndCtl, IDS_TRAVEL1, 3 );
                  return TRUE;

            case default:
                  break;
      }
    return FALSE;
}

This, like the other property page functions in this example, is really just a place holder. We wanted to see the page without becoming involved in exchanging or validating data just yet. There are, however, a couple of significant things about property page functions we should touch on before continuing.

Property sheets communicate with their pages by means of WM_NOTIFY messages and the notification message header structure, NMHDR. We used both of these in the MenuBar example, to aid in properly displaying the drop-button menu.

Notice that, in the previous example code, when we called the handler for the menu bar drop button, the lParam of the WM_NOTIFY is used as pointer to the NMHDR structure and to the LPNMTOOLBAR structure.

            //if the notify message came from our button,
            //recast lParam to a toolbar notify pointer
            //and call the handler for the drop button
            return HandleDropButton( hWnd, (LPNMHDR)lParam,
                                    (LPNMTOOLBAR)lParam);

All notification messages have at least an NMHDR, pointed to by the lParam of the message. The structure is defined in winuser.h, and looks like this:

typedef struct tagNMHDR
{
    HWND  hwndFrom;  //handle of the window the message is from
    UINT  idFrom;    //control id that sent the message
    UINT  code;      //what additional info is appended to this
                     //NMHDR
}   NMHDR;

The NMHDR was designed to be an extensible message format, so that controls could send complex and detailed messages to their parent window. If there is more information than fits in an NMHDR, the header is reallocated, appended with the extra information, and the code field is updated with a value that identifies the type and amount of additional information intended for the target of the notify message.

Context-specific notification messages are how property sheets allow individual pages to do specialized initialization processing, clean up before the property sheet is destroyed, and manage the transfer of focus. Below are some of the more important property sheet notification messages and their meanings.

Table 1—Property Sheet Notification Messages and Their Meanings
Message Meaning
PSN_APPLY The user tapped the OK button and wants to apply changes
PSN_HELP The user tapped the Help button
PSN_KILLACTIVE The page is losing focus, either because another page is gaining the focus or because the user tapped OK
PSN_QUERYCANCEL The user tapped Cancel
PSN_RESET The user tapped Cancel and the page is about to be destroyed
PSN_SETACTIVE The page is about to be activated

The time to validate a page’s data is in response to the PSN_KILLACTIVE notification. If data passes validation, a property page does two things to tell the property sheet that its ready to lose the input focus:

  • The property page sets the return value of its window structure to PSNRET_NOERROR with code like this:
  •       SetWindowLong( hwndThisPage, DWL_MSGRESULT,
                                       PSNRET_NOERROR);
    

  • The property page returns TRUE.

To keep the page from losing focus, set the window structure return value to PSNRET_INVALID_NOCHANGEPAGE:

      SetWindowLong( hwndThisPage, DWL_MSGRESULT,
                                   PSNRET_INVALID_NOCHANGEPAGE);

On a PSN_APPLY notification, a page should get and save control input. You can respond to a PSN_QUERYCANCEL notification either by returning FALSE to allow the closure of the property sheet, or returning TRUE, which will prevent the sheet’s closing.

Looking Ahead

The dialog has assumed a natural role on the desktop as a common front end to data collection applications. Screen size limitation makes dialogs much less effective for this purpose on Windows CE devices. While we can break a dialog into pages and stack them in a property sheet, this has some drawbacks. The users may overlook pages of the property sheet or forget which ones they have visited. If you want to validate input, you must do so before a user leaves a page of the property sheet, and prevent that page from losing focus until the user provides valid input. This has the potential to be very frustrating and confusing to the user.

A large share of these problems arise from the fact that simply making a large Win 32 dialog into a stack of smaller CE-compliant dialogs really isn’t in the spirit of Windows CE. If collecting data from users is our objective, we need to create a user interface that uses screen real estate effectively, but is also relatively self-documenting and easy for the user to navigate. In the next chapter, we’ll learn how to use command bands to create a better data entry interface.

Table 2—Interface Trouble Shooting Tips
Symptoms Possible Causes
Icon isn’t displayed Check icon bitmap dimensions to ensure the image is no more than 16 x 16.
Toolbar bit maps aren’t displayed Bitmaps are the wrong size or contain unsupported colors. Check for contiguous 16-bit images. Use a 2-bit greyscale color palette.
Some tool bar buttons are blank Make sure you added as many bitmaps as buttons. Also make sure the bitmap dimensions of individual images in the bitmap file are correct.
Combo/List Box strings are garbled or absent Make sure the strings are in Unicode before adding them to the control. If they are being converted from ASCII, make sure to translate them using mbstowcs() or similar functions. Check to ensure translation buffers are correctly sized and type. The destination buffer should be typed TCHAR or WCHAR.
Combo boxes or menu bars don’t appear or are truncated in a command bar Check physical sizes to make sure the elements aren’t too large for the screen real estate they are supposed to occupy. For combo boxes, use the return from GetSystemMetrics(SM_CXSCREEN ) to calculate the control’s width.
Combo box won’t allow entry of text wider than its window Specify the CBS_AUTOHSCROLL style in the CommandBar_InsertComboBox() styles parameter.
Menu or other named resource won’t load Use the TEXT() macro to create tring literals that specify resource names, file names, or path names.

Download the Source

PortingDialog.zip

About the Author

Nancy Nicolaisen is a software engineer who has designed and implemented highly modular Windows CE products that include features such as full remote diagnostics, CE-side data compression, dynamically constructed user interface, automatic screen size detection, entry time data validation.

In addition to writing for Developer.com, she has written several books including Making Win 32 Applications Mobile. She has also written numerous articles on programming technology for national publications including Dr. Dobbs, BYTE Magazine, Microsoft Systems Journal, PC Magazine; Computer Shopper, Windows Sources and Databased Advisor.

# # #

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories