Programming Microsoft Windows Ce Net 3Rd [Electronic resources] نسخه متنی

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

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

Programming Microsoft Windows Ce Net 3Rd [Electronic resources] - نسخه متنی

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

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

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






Dialog Boxes

Dialog boxes are windows created by Windows using a template provided by an application. The template describes the type and placement of the controls in the window. The Dialog Manager—the part of Windows that creates and manages dialog boxes—also provides default functionality for switching focus between the controls using the Tab key as well as default actions for the Enter and Escape keys. In addition, Windows provides a default dialog box window class, freeing applications from the necessity of registering a window class for each of the dialog boxes it might create.

Dialog boxes come in two types: modal and modeless. A modal dialog prevents the user from using the application until the dialog box has been dismissed. For example, the File Open and Print dialog boxes are modal. A modeless dialog box can be used interactively with the remainder of the application. The Find dialog box in Microsoft Pocket Word is modeless; the user doesn't need to dismiss it before typing in the main window.

Like other windows, dialog boxes have a window procedure, although the dialog box window procedure is constructed somewhat differently from standard windows procedures. Rather than passing unprocessed messages to the DefWindowProc procedure for default processing, a dialog box procedure returns TRUE if it processed the message and FALSE if it didn't process the message. Windows supplies a default procedure, DefDialogProc, for use in specific cases—that is, for specialized modeless dialog boxes that have their own window classes.


Dialog Box Resource Templates


Most of the time, the description for the size and placement of the dialog box and for the controls is provided via a resource called a dialog template. You can create a dialog template in memory, but unless a program has an overriding need to format the size and shape of the dialog box on the fly, loading a dialog template directly from a resource is a much better choice. As is the case for other resources such as menus, dialog templates are contained in the resource (RC) file. The template is referenced by the application using either its name or its resource ID.

Figure 6-1 shows a dialog box. This dialog box will be used as an example throughout the discussion of how a dialog box works.


Figure 6-1: A simple dialog box

The dialog template for the dialog box in Figure 6-1 is shown here:

GetVal DIALOG discardable 10, 10, 75, 60
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_CENTER
EXSTYLE WS_EX_CAPTIONOKBTN
CAPTION "Enter line number"
BEGIN
LTEXT "Enter &value:" IDD_VALLABEL, 5, 10, 40, 12
EDITTEXT IDD_VALUE, 50, 10, 20, 12, WS_TABSTOP
AUTORADIOBUTTON "&Decimal", IDD_DEC, 5, 25, 60, 12,
WS_TABSTOP | WS_GROUP
AUTORADIOBUTTON "&Hex", IDD_HEX, 5, 40, 60, 12
END

The syntax for a dialog template follows a simple pattern similar to that for a menu resource. First is the name or ID of the resource followed by the keyword DIALOG identifying that what follows is a dialog template. The optional discardable keyword is followed by the position and size of the dialog box. The position specified is, by default, relative to the owner window of the dialog box.

The units of measurement in a dialog box aren't pixels but dialog units. A dialog unit is defined as one-quarter of the average width of the characters in the system font for horizontal units and one-eighth of the height of one character from the same font for vertical units. The goal is to create a unit of measurement independent of the display technology; in practice, dialog boxes still need to be tested in all display resolutions in which the box might be displayed. You can compute a pixel vs. dialog unit conversion using the GetDialogBaseUnits function, but you'll rarely find it necessary. The visual tools that come with most compilers these days isolate a programmer from terms such as dialog units, but it's still a good idea to know just how dialog boxes are described in an RC file.

The STYLE line of code specifies the style flags for the dialog box. The styles include the standard window (WS_xx) style flags used for windows as well as a series of dialog (DS_xx) style flags specific to dialog boxes. Windows CE supports the following dialog box styles:



DS_ABSALIGNPlaces the dialog box relative to the upper left corner of the screen instead of basing the position on the owner window.



DS_CENTERCenters the dialog box vertically and horizontally on the screen.



DS_MODALFRAMECreates a dialog box with a modal dialog box frame that can be combined with a title bar and System menu by specifying the WS_CAPTION and WS_SYSMENU styles.



DS_SETFONTTells Windows to use a nondefault font that is specified in the dialog template.



DS_SETFOREGROUNDBrings the dialog box to the foreground after it's created. If an application not in the foreground displays a dialog box, this style forces the dialog box to the top of the Z-order so that the user will see it.



Most dialog boxes are created with at least some combination of the WS_POPUP, WS_CAPTION, and WS_SYSMENU style flags. The WS_POPUP flag indicates that the dialog box is a top-level window. The WS_CAPTION style gives the dialog box a title bar. A title bar allows the user to drag the dialog box around as well as serving as a site for title text for the dialog box. The WS_SYSMENU style causes the dialog box to have a Close button on the right end of the title bar, thus eliminating the need for a command bar control to provide the Close button. Note that Windows CE uses this flag differently from other versions of Windows, in which the flag indicates that a system menu is to be placed on the left end of the title bar.

The EXSTYLE line of code specifies the extended style flags for the dialog box. For Windows CE, these flags are particularly important. The WS_EX_CAPTIONOKBTN flag tells the dialog manager to place an OK button on the title bar to the immediate left of the Close button. Having both OK and Close (or Cancel) buttons on the title bar saves precious space in dialog boxes that are displayed on the small screens typical of Windows CE devices. The WS_EX_CONTEXTHELP extended style places a Help button on the title bar to the immediate left of the OK button. Clicking on this button results in a WM_HELP message being sent to the dialog box procedure.

The CAPTION line of code specifies the title bar text of the dialog, provided that the WS_CAPTION style was specified so that the dialog box would have a title bar.

The lines describing the type and placement of the controls in the dialog box are enclosed in BEGIN and END keywords. Each control is specified either by a particular keyword, in the case of commonly used controls, or by the keyword CONTROL, which is a generic placeholder that can specify any window class to be placed in the dialog box. The LTEXT line of code on the previous page specifies a static left-justified text control. The keyword is followed by the default text for the control in quotes. The next parameter is the ID of the control, which must be unique for the dialog box. In this template, the ID is a constant defined in an include file that is included by both the resource script and the C or C++ file containing the dialog box procedure.

The next four values are the location and size of the control, in dialog units, relative to the upper left corner of the dialog box. Following that, any explicit style flags can be specified for the control. In the case of the LTEXT line, no style flags are necessary, but as you can see, the EDITTEXT and first AUTORADIOBUTTON entries each have style flags specified. Each of the control keywords have subtly different syntax. For example, the EDITTEXT line doesn't have a field for default text. The style flags for the individual controls deserve notice. The edit control and the first of the two radio buttons have a WS_TABSTOP style. The dialog manager looks for controls with the WS_TABSTOP style to determine which control gets focus when the user presses the Tab key. In this example, pressing the Tab key results in focus being switched between the edit control and the first radio button.

The WS_GROUP style on the first radio button starts a new group of controls. All the controls following the radio button are grouped together, up to the next control that has the WS_GROUP style. Grouping auto radio buttons allows only one radio button at a time to be selected.

Another benefit of grouping is that focus can be changed among the controls within a group by exploiting the cursor keys as well as the Tab key. The first member of a group should have a WS_TABSTOP style; this allows the user to tab to the group of controls and then use the cursor keys to switch the focus among the controls in the group.

The CONTROL statement isn't used in this example, but it's important and merits some explanation. It's a generic statement that allows inclusion of any window class in a dialog box. It has the following syntax:

CONTROL "text", id, class, style, x, y, width, height
[, extended-style]

For this entry, the default text and control ID are similar to the other statements, but the next field, class, is new. It specifies the window class of the control you want to place in the dialog box. The class field is followed by the style flags and then by the location and size of your control. Finally, the CONTROL statement has a field for extended style flags. If you use eMbedded Visual C++ to create a dialog box and look at the resulting RC file using a text editor, you'll see that it uses CONTROL statements as well as the more readable LTEXT, EDITTEXT, and BUTTON statements. There's no functional difference between an edit control created with a CONTROL statement and one created with an EDITTEXT statement. The CONTROL statement is a generic version of the more specific keywords. The CONTROL statement also allows inclusion of controls that don't have a special keyword associated with them.


Creating a Dialog Box


Creating and displaying a dialog box is simple; just use one of the many dialog box creation functions. The first two are these:

int DialogBox (HANDLE hInstance, LPCTSTR lpTemplate, HWND hWndOwner,
DLGPROC lpDialogFunc);
int DialogBoxParam (HINSTANCE hInstance, LPCTSTR lpTemplate,
HWND hWndOwner, DLGPROC lpDialogFunc,
LPARAM dwInitParam);

These two functions differ only in DialogBoxParam's additional LPARAM parameter, so I'll talk about them at the same time. The first parameter to these functions is the instance handle of the program. The second parameter specifies the name or ID of the resource containing the dialog template. As with other resources, to specify a resource ID instead of a name requires the use of the MAKEINTRESOURCE macro.

The third parameter is the handle of the window that will own the dialog box. The owning window isn't the parent of the dialog box because, were that true, the dialog box would be clipped to fit inside the parent. Ownership means instead that the dialog box will be hidden when the owner window is minimized and will always appear above the owner window in the Z-order.

The fourth parameter is a pointer to the dialog box procedure for the dialog box. I'll describe the dialog box procedure shortly. The DialogBoxParam function has a fifth parameter, which is a user-defined value that's passed to the dialog box procedure when the dialog box is to be initialized. This helpful value can be used to pass a pointer to a structure of data that can be referenced when your application is initializing the dialog box controls.

Two other dialog box creation functions create modal dialogs. They are the following:

int DialogBoxIndirect (HANDLE hInstance, LPDLGTEMPLATE lpTemplate,
HWND hWndParent, DLGPROC lpDialogFunc);
int DialogBoxIndirectParam (HINSTANCE hInstance,
LPCDLGTEMPLATE DialogTemplate, HWND hWndParent,
DLGPROC lpDialogFunc, LPARAM dwInitParam);

The difference between these two functions and the two previously described is that these two use a dialog box template in memory to define the dialog box rather than using a resource. This allows a program to dynamically create a dialog box template on the fly. The second parameter to these functions points to a DLGTEMPLATE structure, which describes the overall dialog box window, followed by an array of DLGITEMTEMPLATE structures defining the individual controls.

When any of these four functions are called, the dialog manager creates a modal dialog box using the template passed. The window that owns the dialog is disabled, and the dialog manager then enters its own internal GetMessage/DispatchMessage message processing loop; this loop doesn't exit until the dialog box is destroyed. Because of this, these functions don't return to the caller until the dialog box has been destroyed. The WM_ENTERIDLE message that's sent to owner windows in other versions of Windows while the dialog box is displayed isn't supported under Windows CE.

If an application wanted to create a modal dialog box with the template shown above and pass a value to the dialog box procedure, it might call this:

DialogBoxParam (hInstance, TEXT ("GetVal"), hWnd, GetValDlgProc,
0x1234);

The hInstance and hWnd parameters would be the instance handle of the application and the handle of the owner window. The GetVal string is the name of the dialog box template, while GetValDlgProc is the name of the dialog box procedure. Finally, 0x1234 is an application-defined value. In this case, it might be used to provide a default value in the dialog box.


Dialog Box Procedures


The final component necessary for a dialog box is the dialog box procedure. As in the case of a window procedure, the purpose of the dialog box procedure is to field messages sent to the window—in this case, a dialog box window—and perform the appropriate processing. In fact, a dialog box procedure is simply a special case of a window procedure, although we should pay attention to a few differences between the two.

The first difference, as mentioned in the previous section, is that a dialog box procedure doesn't pass unprocessed messages to DefWindowProc. Instead, the procedure returns TRUE for messages it processes and FALSE for messages that it doesn't process. The dialog manager uses this return value to determine whether the message needs to be passed to the default dialog box procedure.

The second difference from standard window procedures is the addition of a new message, WM_INITDIALOG. Dialog box procedures perform any initialization of the controls during the processing of this message. Also, if the dialog box was created with DialogBoxParam or DialogBoxIndirectParam, the lParam value is the generic parameter passed during the call that created the dialog box. While it might seem that the controls could be initialized during the WM_CREATE message, that doesn't work. The problem is that during the WM_CREATE message, the controls on the dialog box haven't yet been created, so they can't be initialized. The WM_INITDIALOG message is sent after the controls have been created and before the dialog box is made visible, which is the perfect time to initialize the controls.

Here are a few other minor differences between a window procedure and a dialog box procedure. Most dialog box procedures don't need to process the WM_PAINT message because any necessary painting is done by the controls or, in the case of owner-draw controls, in response to control requests. Most of the code in a dialog box procedure is responding to WM_COMMAND messages from the controls. As with menus, the WM_COMMAND messages are parsed by the control ID values. Two special predefined ID values that a dialog box has to deal with are IDOK and IDCANCEL. IDOK is assigned to the OK button on the title bar of the dialog box, while IDCANCEL is assigned to the Close button. In response to a click of either button, a dialog box procedure should call

BOOL EndDialog (HWND hDlg, int nResult);

EndDialog closes the dialog box and returns control to the caller of whatever function created the dialog box. The hDlg parameter is the handle of the dialog box, while the nResult parameter is the value that's passed back as the return value of the function that created the dialog box.

The difference, of course, between handling the IDOK and IDCANCEL buttons is that if the OK button is clicked, the dialog box procedure should collect any relevant data from the dialog box controls to return to the calling procedure before it calls EndDialog.

A dialog box procedure to handle the GetVal template previously described is shown here:

//======================================================================
// GetVal Dialog procedure
//
BOOL CALLBACK GetValDlgProc (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
TCHAR szText[64];
int nVal, nBase;
switch (wMsg) {
case WM_INITDIALOG:
SetDlgItemInt (hWnd, IDD_VALUE, 0, TRUE);
SendDlgItemMessage (hWnd, IDD_VALUE, EM_LIMITTEXT,
sizeof (szText)-1, 0);
CheckRadioButton (hWnd, IDD_DEC, IDD_HEX, IDD_DEC);
return TRUE;
case WM_COMMAND:
switch (LOWORD (wParam)) {
case IDD_HEX:
// See if Hex already checked.
if (SendDlgItemMessage (hWnd, IDD_HEX,
BM_GETSTATE, 0, 0) == BST_CHECKED)
return TRUE;
// Get text from edit control.
GetDlgItemText (hWnd, IDD_VALUE, szText, sizeof (szText));
// Convert value from decimal, and then set as hex.
if (ConvertValue (szText, 10, &nVal)) {
// If conversion successful, set new value.
wsprintf (szText, TEXT ("%X"), nVal);
SetDlgItemText (hWnd, IDD_VALUE, szText);
// Set radio button.
CheckRadioButton (hWnd, IDD_DEC, IDD_HEX, IDD_HEX);
} else {
MessageBox (hWnd, TEXT ("Value not valid"),
TEXT ("Error"), MB_OK);
}
return TRUE;
case IDD_DEC:
// See if Decimal already checked.
if (SendDlgItemMessage (hWnd, IDD_DEC,
BM_GETSTATE, 0, 0) == BST_CHECKED)
return TRUE;
// Get text from edit control.
GetDlgItemText (hWnd, IDD_VALUE, szText, sizeof (szText));
// Convert value from hex, then set as decimal.
if (ConvertValue (szText, 16, &nVal)) {
// If conversion successful, set new value.
wsprintf (szText, TEXT ("%d"), nVal);
SetDlgItemText (hWnd, IDD_VALUE, szText);
// Set radio button.
CheckRadioButton (hWnd, IDD_DEC, IDD_HEX, IDD_DEC);
} else {
// If bad conversion, tell user.
MessageBox (hWnd, TEXT ("Value not valid"),
TEXT ("Error"), MB_OK);
}
return TRUE;
case IDOK:
// Get the current text.
GetDlgItemText (hWnd, IDD_VALUE, szText, sizeof (szText));
// See which radio button checked.
if (SendDlgItemMessage (hWnd, IDD_DEC,
BM_GETSTATE, 0, 0) == BST_CHECKED)
nBase = 10;
else
nBase = 16;
// Convert the string to a number.
if (ConvertValue (szText, nBase, &nVal))
EndDialog (hWnd, nVal);
else
MessageBox (hWnd, TEXT ("Value not valid"),
TEXT ("Error"), MB_OK);
break;
case IDCANCEL:
EndDialog (hWnd, 0);
return TRUE;
}
break;
}
return FALSE;
}

This is a typical example of a dialog box procedure for a simple dialog box. The only messages that are processed are the WM_INITDIALOG and WM_COMMAND messages. The WM_INITDIALOG message is used to initialize the edit control using a number passed, via DialogBoxParam, through to the lParam value. The radio button controls aren't auto radio buttons because the dialog box procedure needs to prevent the buttons from changing if the value in the entry field is invalid. The WM_COMMAND message is parsed by the control ID, where the appropriate processing takes place. The IDOK and IDCANCEL buttons aren't in the dialog box template; as mentioned earlier, those buttons are placed by the dialog manager in the title bar of the dialog box.


Modeless Dialog Boxes


I've talked so far about modal dialog boxes that prevent the user from using other parts of the application before the dialog box is dismissed. Modeless dialog boxes, on the other hand, allow the user to work with other parts of the application while the dialog box is still open. Creating and using modeless dialog boxes requires a bit more work. For example, you create modeless dialog boxes using different functions than those for modal dialog boxes:

HWND CreateDialog (HINSTANCE hInstance, LPCTSTR lpTemplate,
HWND hWndOwner, DLGPROC lpDialogFunc);
HWND CreateDialogParam (HINSTANCE hInstance, LPCDLGTEMPLATE lpTemplate,
HWND hWndOwner, DLGPROC lpDialogFunc,
LPARAM lParamInit);
HWND CreateDialogIndirect (HINSTANCE hInstance,
LPCDLGTEMPLATE lpTemplate, HWND hWndOwner,
DLGPROC lpDialogFunc);

or

HWND CreateDialogIndirectParam (HINSTANCE hInstance,
LPCDLGTEMPLATE lpTemplate, HWND hWndOwner,
DLGPROC lpDialogFunc, LPARAM lParamInit);

The parameters in these functions mirror the creation functions for the modal dialog boxes with similar parameters. The difference is that these functions return immediately after creating the dialog boxes. Each function returns 0 if the create failed or returns the handle to the dialog box window if the create succeeded.

The handle returned after a successful creation is important because applications that use modeless dialog boxes must modify their message loop code to accommodate the dialog box. The new message loop should look similar to the following:

while (GetMessage (&msg, NULL, 0, 0)) {
if ((hMlDlg == 0) || (!IsDialogMessage (hMlDlg, &msg))) {
TranslateMessage (&msg);
DispatchMessage (&msg);
}
}

The difference from a modal dialog box message loop is that if the modeless dialog box is being displayed, messages should be checked to see whether they're dialog messages. If they're not dialog messages, your application forwards them to TranslateMessage and DispatchMessage. The code shown above simply checks to see whether the dialog box exists by checking a global variable containing the handle to the modeless dialog box and, if it's not 0, calls IsDialogMessage. If IsDialogMessage doesn't translate and dispatch the message itself, the message is sent to the standard TranslateMessage/DispatchMessage body of the message loop. Of course, this code assumes that the handle returned by CreateDialog (or whatever function creates the dialog box) is saved in hMlDlg and that hMlDlg is set to 0 when the dialog box is closed.

Another difference between modal and modeless dialog boxes is in the dialog box procedure. Instead of using EndDialog to close the dialog box, you must call DestroyWindow instead. This is because EndDialog is designed to work only with the internal message loop processing that's performed with a modal dialog box. Finally, an application usually won't want more than one instance of a modeless dialog box displayed at a time. An easy way to prevent this is to check the global copy of the window handle to see whether it's nonzero before calling CreateDialog. To do this, the dialog box procedure must set the global handle to 0 after it calls DestroyWindow.


Property Sheets


To the user, a property sheet is a dialog box with one or more tabs across the top that allow the user to switch among different "pages" of the dialog box. To the programmer, a property sheet is a series of stacked dialog boxes. Only the top dialog box is visible; the dialog manager is responsible for displaying the dialog box associated with the tab on which the user clicks. However you approach property sheets, they're invaluable given the limited screen size of Windows CE devices.

Each page of the property sheet, named appropriately enough a property page, is a dialog box template, either loaded from a resource or created dynamically in memory. Each property page has its own dialog box procedure. The frame around the property sheets is maintained by the dialog manager, so the advantages of property sheets come with little overhead to the programmer. Unlike the property sheets supported in other versions of Windows, the property sheets in Windows CE don't support the Apply button. Also, the OK and Cancel buttons for the property sheet are contained in the title bar, not positioned below the pages.

Creating a Property Sheet


Instead of the dialog box creation functions, use this new function to create a property sheet:

int PropertySheet (LPCPROPSHEETHEADER lppsph);

The PropertySheet function creates the property sheet according to the information contained in the PROPSHEETHEADER structure, which is defined as the following:

typedef struct _PROPSHEETHEADER {
DWORD dwSize;
DWORD dwFlags;
HWND hwndOwner;
HINSTANCE hInstance;
union {
HICON hIcon;
LPCWSTR pszIcon;
};
LPCWSTR pszCaption;
UINT nPages;
union {
UINT nStartPage;
LPCWSTR pStartPage;
};
union {
LPCPROPSHEETPAGE ppsp;
HPROPSHEETPAGE FAR *phpage;
};
PFNPROPSHEETCALLBACK pfnCallback;
} PROPSHEETHEADER;

Filling in this convoluted structure isn't as imposing a task as it might look. The dwSize field is the standard size field that must be initialized with the size of the structure. The dwFlags field contains the creation flags that define how the property sheet is created, which fields of the structure are valid, and how the property sheet behaves. Some of the flags indicate which fields in the structure are used. (I'll talk about those flags when I describe the other fields.) Two other flags set the behavior of the property sheet. The PSH_PROPTITLE flag appends the string "Properties" to the end of the caption specified in the pszCaption field. The PSH_MODELESS flag causes the PropertySheet function to create a modeless property sheet and immediately return. A modeless property sheet is like a modeless dialog box; it allows the user to switch back to the original window while the property sheet is still being displayed.

The next two fields are the handle of the owner window and the instance handle of the application. Neither the hIcon nor the pszIcon field is used in Windows CE, so both fields should be set to 0. The pszCaption field should point to the title bar text for the property sheet. The nStartPage/pStartPage union should be set to indicate the page that should be initially displayed. This can be selected either by number or by title if the PSH_USEPSTARTPAGE flag is set in the dwFlags field.

The ppsp/phpage union points to either an array of PROPSHEETPAGE structures describing each of the property pages or handles to previously created property pages. For either of these, the nPages field must be set to the number of entries of the array of structures or page handles. To indicate that the pointer points to an array of PROPSHEETPAGE structures, set the PSH_PROPSHEETPAGE flag in the dwFlags field. I'll describe both the structure and how to create individual pages shortly.

The pfnCallBack field is an optional pointer to a procedure that's called twice—when the property sheet is about to be created and again when it's about to be initialized. The callback function allows applications to fine-tune the appearance of the property sheet. This field is ignored unless the PSP_USECALLBACK flag is set in the dwFlags field. One place the callback is used is in Pocket PC applications, to place the tabs on the bottom of the property sheet.

The callback procedure should be defined to match the following prototype:

UINT CALLBACK PropSheetPageProc (HWND hwnd, UINT uMsg, 
LPPROPSHEETPAGE ppsp);

The parameters sent back to the application are a handle value documented to be reserved, the notification code in the uMsg parameter, and, in some notifications, a pointer to a PROPSHEETPAGE structure. The notifications supported in Windows CE are as follows:



PSCB_PRECREATESent just before the property sheet is created



PSCB_INITIALIZEDSent when the property sheet is initialized



PSCB_GETVERSIONSent to query the level of support expected by the application



PSCB_GETTITLESent to query additional title text



PSCB_GETLINKTEXTOn Pocket PC, sent to query the string to place below the tabbed pages on the property sheet



Creating a Property Page


As I mentioned earlier, individual property pages can be specified by an array of PROPSHEETPAGE structures or an array of handles to existing property pages. Creating a property page is accomplished with a call to the following:

HPROPSHEETPAGE CreatePropertySheetPage (LPCPROPSHEETPAGE lppsp);

This function is passed a pointer to the same PROPSHEETPAGE structure and returns a handle to a property page. PROPSHEETPAGE is defined as this:

typedef struct _PROPSHEETPAGE {
DWORD dwSize;
DWORD dwFlags;
HINSTANCE hInstance;
union {
LPCSTR pszTemplate;
LPCDLGTEMPLATE pResource;
};
union {
HICON hIcon;
LPCSTR pszIcon;
};
LPCSTR pszTitle;
DLGPROC pfnDlgProc;
LPARAM lParam;
LPFNPSPCALLBACK pfnCallback;
UINT FAR * pcRefParent;
} PROPSHEETPAGE;

The structure looks similar to the PROPSHEETHEADER structure, leading with a dwSize and a dwFlags field followed by an hInstance field. In this structure, hInstance is the handle of the module from which the resources will be loaded. The dwFlags field again specifies which fields of the structure are used and how they're used, as well as a few flags specifying the characteristics of the page itself.

The pszTemplate/pResource union specifies the dialog box template used to define the page. If the PSP_DLGINDIRECT flag is set in the dwFlags field, the union points to a dialog box template in memory. Otherwise, the field specifies the name of a dialog box resource. The hIcon/pszIcon union isn't used in Windows CE and should be set to 0. If the dwFlags field contains a PSP_USETITLE flag, the pszTitle field points to the text used on the tab for the page. Otherwise, the tab text is taken from the caption field in the dialog box template. The pfnDlgProc field points to the dialog box procedure for this specific page, and the lParam field is an application-defined parameter that can be used to pass data to the dialog box procedure. The pfnCallback field can point to a callback procedure that's called twice—when the page is about to be created and when it's about to be destroyed. Again, like the callback for the property sheet, the property page callback allows applications to fine-tune the page characteristics. This field is ignored unless the dwFlags field contains the PSP_USECALLBACK flag. Finally, the pcRefCount field can contain a pointer to an integer that will store a reference count for the page. This field is ignored unless the flags field contains the PSP_USEREFPARENT flag.

Windows CE supports the PSP_PREMATURE flag, which causes a property page to be created when the property sheet that owns it is created. Normally, a property page isn't created until the first time it's shown. This has an impact on property pages that communicate and cooperate with each other. Without the PSP_PREMATURE flag, the only property page that's automatically created when the property sheet is created is the page that is displayed first. So at that moment, that first page has no sibling pages to communicate with. Using the PSP_PREMATURE flag, you can ensure that a page is created when the property sheet is created, even though it isn't the first page in the sheet. Although it's easy to get overwhelmed by all these structures, simply using the default values and not using the optional fields results in a powerful and easily maintainable property sheet that's also as easy to construct as a set of individual dialog boxes.

Once a property sheet has been created, the application can add and delete pages. The application adds a page by sending a PSM_ADDPAGE message to the property sheet window. The message must contain the handle of a previously created property page in lParam; wParam isn't used. Likewise, the application can remove a page by sending a PSM_REMOVEPAGE message to the property sheet window. The application specifies a page for deletion either by setting wParam to the zero-based index of the page selected for removal or by passing the handle to that page in lParam.

The code below creates a simple property sheet with three pages. Each of the pages references a dialog box template resource. As you can see, most of the initialization of the structures can be performed in a fairly mechanical fashion.

PROPSHEETHEADER psh;
PROPSHEETPAGE psp[3];
int i;
// Initialize page structures with generic information.
memset (&psp, 0, sizeof (psp)); // Zero out all unused values.
for (i = 0; i < dim(psp); i++) {
psp[i].dwSize = sizeof (PROPSHEETPAGE);
psp[i].dwFlags = PSP_DEFAULT; // No special processing needed
psp[i].hInstance = hInst; // Instance handle where the
} // dialog templates are located
// Now do the page-specific stuff.
psp[0].pszTemplate = TEXT ("Page1"); // Name of dialog resource for page 1
psp[0].pfnDlgProc = Page1DlgProc; // Pointer to dialog proc for page 1
psp[1].pszTemplate = TEXT ("Page2"); // Name of dialog resource for page 2
psp[1].pfnDlgProc = Page2DlgProc; // Pointer to dialog proc for page 2
psp[2].pszTemplate = TEXT ("Page3"); // Name of dialog resource for page 3
psp[2].pfnDlgProc = Page3DlgProc; // Pointer to dialog proc for page 3
// Init property sheet header structure.
psh.dwSize = sizeof (PROPSHEETHEADER);
psh.dwFlags = PSH_PROPSHEETPAGE; // We are using templates, not handles.
psh.hwndParent = hWnd; // Handle of the owner window
psh.hInstance = hInst; // Instance handle of the application
psh.pszCaption = TEXT ("Property sheet title");
psh.nPages = dim(psp); // Number of pages
psh.nStartPage = 0; // Index of page to be shown first
psh.ppsp = psp; // Pointer to page structures
psh.pfnCallback = 0; // We don't need a callback procedure.
// Create property sheet. This returns when the user dismisses the sheet
// by tapping OK or the Close button.
i = PropertySheet (&psh);

While this fragment has a fair amount of structure filling, it's boilerplate code. Everything not defined, such as the page dialog box resource templates and the page dialog box procedures, is required for dialog boxes as well as property sheets. So aside from the boilerplate stuff, property sheets require little, if any, work beyond simple dialog boxes.

Property Page Procedures


The procedures that back up each of the property pages differ in only a few ways from standard dialog box procedures. First, as I mentioned previously, unless the PSP_PREMATURE flag is used, pages aren't created immediately when the property sheet is created. Instead, each page is created and WM_INITDIALOG messages are sent only when the page is initially shown. Also, the lParam parameter doesn't point to a user-defined parameter; instead, it points to the PROPSHEETPAGE structure that defined the page. Of course, that structure contains a user-definable value that can be used to pass data to the dialog box procedure.

Also, a property sheet procedure doesn't field the IDOK and IDCANCEL control IDs for the OK and Close buttons on a standard dialog box. These buttons instead are handled by the system-provided property sheet procedure that coordinates the display and management of each page. When the OK or Close button is tapped, the property sheet sends a WM_NOTIFY message to each sheet notifying them that one of the two buttons has been tapped and that they should acknowledge that it's okay to close the property sheet.

Switching Pages


When a user switches from one page to the next, the Dialog Manager sends a WM_NOTIFY message with the code PSN_KILLACTIVE to the page currently being displayed. The dialog box procedure should then validate the data on the page. If it's permissible for the user to change the page, the dialog box procedure should then set the return value of the window structure of the page to PSNRET_NOERROR and return TRUE. You set the PSNRET_NOERROR return field by calling SetWindowLong with DWL_MSGRESULT, as in the following line of code:

SetWindowLong (hwndPage, DWL_MSGRESULT, PSNRET_NOERROR);

where hwndPage is the handle of the property sheet page. A page can keep focus by returning PSNRET_INVALID_NOCHANGEPAGE in the return field. Assuming a page has indicated that it's okay to lose focus, the page being switched to receives a PSN_SETACTIVE notification via a WM_NOTIFY message. The page can then accept the focus or specify another page that should receive the focus.

Closing a Property Sheet


When the user taps on the OK button, the property sheet procedure sends a WM_NOTIFY with the notification code PSN_KILLACTIVE to the page currently being displayed, followed by a WM_NOTIFY with the notification code PSN_APPLY to each of the pages that have been created. Each page procedure should save any data from the page controls when it receives the PSN_APPLY notification code.

When the user clicks the Close button, a PSN_QUERYCANCEL notification is sent to the page procedure of the page currently being displayed. All this notification requires is that the page procedure return TRUE to prevent the close or FALSE to allow the close. A further notification, PSN_RESET, is then sent to all the pages that have been created, indicating that the property sheet is about to be destroyed.


Common Dialogs


In the early days of Windows, it was a rite of passage for a Windows developer to write his or her own File Open dialog box. A File Open dialog box is complex—it must display a list of the possible files from a specific directory, allow file navigation, and return a fully justified filename back to the application. While it was great for programmers to swap stories about how they struggled with their unique implementation of a File Open dialog, it was hard on the users. Users had to learn a different file open interface for every Windows application.

Windows now provides a set of common dialog boxes that perform typical functions, such as selecting a filename to open or save or picking a color. These standard dialog boxes (called common dialogs) serve two purposes. First, common dialogs lift from developers the burden of having to create these dialog boxes from scratch. Second, and just as important, common dialogs provide a common interface to the user across different applications. (These days, Windows programmers swap horror stories about learning COM.)

Windows CE provides four common dialogs: File Open, Save As, Print, and Choose Color. Common dialogs, such as Find, Choose Font, and Page Setup, that are available under other versions of Windows aren't supported under Windows CE. The other advantage of the common dialogs is that they have a customized look for each platform while retaining the same programming interface. This makes it easy to use, say, the File Open dialog on the Pocket PC, the Smartphone, and embedded versions of Windows CE because the dialog box has the same interface on both systems, even though the look of the dialog box is vastly different on the different platforms. Figure 6-2 shows the File Open dialog on an embedded Windows CE system; Figure 6-3 shows the File Open dialog box on the Pocket PC.


Figure 6-2: The File Open dialog on an embedded Windows CE system


Figure 6-3: The File Open dialog on a Pocket PC

Instead of showing you how to use the common dialogs here, I'll let the next example program, DlgDemo, show you. That program demonstrates all four supported common dialog boxes.

/ 169