- Table of Contents
- Copyright
- About the Authors
- About the Contributors
- Acknowledgments
- Tell Us What You Think!
- Introduction
- How to Use This Book
- What You Need to Use This Book
- What's New in Visual C++ 6.0
- Contacting the Main Author
- Part I: Introduction
- Chapter 1. The Visual C++ 6.0 Environment
- Part II: MFC Programming
- Chapter 2. MFC Class Library Overview
- Chapter 3. MFC Message Handling Mechanism
- Chapter 4. The Document View Architecture
- Chapter 5. Creating and Using Dialog Boxes
- Chapter 6. Working with Device Contexts and GDI Objects
- Chapter 7. Creating and Using Property Sheets
- Understanding Property Sheets
- Creating a Property Sheet
- Responding to Property Sheet Messages
- Customizing the Standard Property Sheet
- Understanding the Win32 Sequence of Events
- Summary
- Chapter 8. Working with the File System
- Chapter 9. Using Serialization with File and Archive Objects
- Part III: Internet Programming with MFC
- Chapter 10. MFC and the Internet Server API (ISAPI)
- Chapter 11. The WinInet API
- Chapter 12. MFC HTML Support
- Part IV: Advanced Programming Topics
- Chapter 13. Using the Standard C++ Library
- Chapter 14. Error Detection and Exception Handling Techniques
- Chapter 15. Debugging and Profiling Strategies
- Chapter 16. Multithreading
- Chapter 17. Using Scripting and Other Tools to Automate the Visual C++ IDE
- Part V: Database Programming
- Chapter 18. Creating Custom AppWizards
- Chapter 19. Database Overview
- Chapter 20. ODBC Programming
- Chapter 21. MFC Database Classes
- Chapter 22. Using OLE DB
- Chapter 23. Programming with ADO
- Part VI: MFC Support for COM and ActiveX
- Chapter 24. Overview of COM and Active Technologies
- Chapter 25. Active Documents
- Chapter 26. Active Containers
- Chapter 27. Active Servers
- Chapter 28. ActiveX Controls
- Part VII: Using the Active Template Library
- Chapter 29. ATL Architecture
- Chapter 30. Creating COM Objects Using ATL
- Chapter 31. Creating ActiveX Controls Using ATL
- Chapter 32. Using ATL to Create MTS and COM+ Components
- Part VIII: Finishing Touches
- Chapter 33. Adding Windows Help
- Part IX: Appendix
Customizing the Standard Property Sheet
A number of CPropertySheet member functions let you change the standard property sheet appearance, and few functions can radically change the standard functionality (such as setting the Wizard mode, which is discussed in a following section).
You would normally set the property sheet title in the constructor, but you can use the SetTitle() member function to change this title. SetTitle() requires two parameters. The first is a pointer to the new caption, and the second lets you optionally pass a PSH_PROPTITLE flag to prefix your title with the text "Properties for".
When the tabs of a property sheet can't all be displayed along one line, the default action is to stack them to produce rows of tabs, as Figure 7.6 shows.
Figure 7.6 A property sheet with stacked tabs.
You can change this behavior by calling the property sheet's EnableStackedTabs() function and passing a FALSE value. This action displays all the tabs along the same line and adds two scroll arrows to let the user scroll through the tabs, as Figure 7.7 shows.
Figure 7.7 A property sheet with scrollable tabs.
Property Page Management
You can add a page at any time during the life of a property sheet, even after it has been displayed (although this obviously is easier with a modeless property sheet). The tab control is updated automatically after you call AddPage() so that the user can select it. Similarly, you can use RemovePage() and pass a zero-based page index number or a pointer to a specific page to remove that page.
If you find the zero-based index for a specific page, you can use the GetPageIndex() function to return the index by passing the function a pointer to one of the sheet's CPropertyPage objects. The corresponding GetPage() function returns a pointer to the CPropertyPage object specified by an index value.
You can find the number of pages a property sheet holds, which is returned from the sheet's GetPageCount() function.
The current active page number is returned from GetActiveIndex(). If you'd prefer a pointer to the active page, you can find this directly by calling GetActivePage(). The corresponding SetActivePage() lets you set a new active page specified by a zero-based index or a pointer to the desired page.
You can access and change the property sheet's tab control using the GetTabControl() function. This function returns a CTabCtrl pointer to the sheet's tab control object. Using this pointer, you can manipulate the full functionality of the tab control to add images to the tabs via SetImageList(). You can call GetTabControl() only after the property sheet window is opened, and the tab control is guaranteed to exist. This means that you can't call GetTabControl() before calling DoModal(), since the property sheet isn't created except during the call to DoModal(). Fortunately, an OnInitDialog() override in the CPropertySheet class comes to the rescue, providing an excellent place to manipulate the tab control before the window is displayed.
For example, the following property sheet OnInitDialog() override adds an image to each tab from an image list (of 8x8 pixel images) held as a bitmap in IDB_IMAGE_LIST:
BOOL CImagePropertySheet::OnInitDialog()
{
BOOL bResult = CPropertySheet::OnInitDialog();
m_ImageList.Create(IDB_IMAGE_LIST,8,8,RGB(255,255,255));
CTabCtrl* pTabCtrl = GetTabControl();
if (pTabCtrl)
{
pTabCtrl->SetImageList(&m_ImageList);
for(int i=0;i<pTabCtrl->GetItemCount();i++)
{
TCITEM tcItem;
tcItem.mask = TCIF_IMAGE;
tcItem.iImage = i;
pTabCtrl->SetItem(i,&tcItem);
}
}
return bResult;
}
The m_ImageList member in this listing is defined as a CImageList object in the CImagePropertySheet class definition. The example iterates through the tabs, setting one of the images against each tab.
Creating a Wizard Mode Property Sheet
Wizards are property sheets with Back and Next buttons instead of tabs to allow users to sequentially traverse the property pages. You'll usually see these buttons used in installation and configuration tasks where users can proceed to the next step only after they've successfully completed the current page.
Turning a normal property sheet into a wizard is simple. Just one call to the property sheet's SetWizardMode() function is required before the sheet is displayed, and instantly the property sheet is transformed into a wizard (see Figure 7.8)!
Figure 7.8 A Wizard mode property sheet.
You'll probably want to disable some of the buttons at various stages, and display a Finish button on the last property page. The property sheet's SetWizardButtons() function lets you do just that. You can pass and combine a set of flag values to specify the required customization, as Table 7.1 shows.
Table 7.1. Flag Values for Customizing the Wizard Buttons
| Flag Value | Description |
| PSWIZB_NEXT | Enable the Next> button. |
| PSWIZB_BACK | Enable the <Back button. |
| PSWIZB_DISABLEDFINISH | Show a disabled Finish button instead of the Next> button. |
| PSWIZB_FINISH | Show the Finish button instead of the Next> button. |
You must call SetWizardButtons() only after the property sheet has been displayed, because it manipulates the property sheet buttons directly. The OnSetActive() virtual function in the property sheet is a good candidate for calling SetWizardButtons(). You can use the window's GetParent() function to get to the property sheet object from a property page and access SetWizardButtons() through the pointer (after casting it) returned from GetParent().
The following code fragment demonstrates these principles by overriding OnSetActive() in a derived CPropertyPage class. The example assumes that the same class (or base class) will be used to implement all the property pages in a property sheet. If this is true, the same function can test the current page to check whether it is at the start or the end of a wizard sequence. At the start, it disables the <Back button; at the end, it displays the Finish button:
BOOL CWizProppage::OnSetActive()
{
CPropertySheet* pParentSheet
= STATIC_DOWNCAST(CPropertySheet,GetParent());
const int nPageNo = pParentSheet->GetActiveIndex();
// Is this the first page, if so just enable next
if (nPageNo == 0)
pParentSheet->SetWizardButtons(PSWIZB_NEXT);
// Is this the last page, if so just enable back and show finish
if (nPageNo == pParentSheet->GetPageCount()-1)
pParentSheet->SetWizardButtons(PSWIZB_BACK|PSWIZB_FINISH);
return CPropertyPage::OnSetActive();
}
You can further customize the Finish button by changing its caption with a call to SetFinishText() and passing it a pointer to the required caption text. SetFinishText() also has the effect of removing the <Back button, so that only the button with your text and the Cancel button are displayed.
Using the New CPropertySheetEx and CPropertyPageEx Classes
The Visual C++ 6 MFC provides two new property sheet and page classes: CPropertySheetEx and CPropertyPageEx. These are introduced to support the new features included with Windows NT 5 and Windows 98 (and will only work properly on these platforms). The new features let you set a background bitmap in a property sheet and let property pages display both titles and subtitles. At the time of writing, however, these features are available only for wizard-style property sheets.
CPropertySheetEx extends CPropertySheet, and the only real difference is the additional constructor parameters. The first three parameters—the caption, parent window, and initial page—are identical. The next three (optional) parameters let you specify an HBITMAP handle for the background bitmap, an HPALETTE handle for the bitmap's palette, and an HBITMAP handle for the property sheet's header section. You can pass a NULL value to any of these parameters if the parameter isn't required.
There is a corresponding Construct() function with parameters identical to the constructors for vectoring property sheets, and an AddPage() function that takes a pointer to the new CPropertyPageEx pages.
You can access the CPropertySheetEx class's m_psh member to change the Win32 PROPSHEETHEADER structure directly. You must add the PSH_WIZARD97 flag value to the dwFlags member of this structure before displaying the property sheet to use any of the new features.
A set of associated flag values lets you tailor the specific details of the new wizard look (see Table 7.2). These flags can be combined with a logical OR to add or remove certain presentational aspects of the Wizard97 property sheet style.
Table 7.2. Flag Values for Customizing the Wizard97 Property Sheet
| Flag Value | Description |
| PSH_WIZARD97 | Sets the Wizard97 mode. You must always set this flag if you want to use the new wizard features. |
| PSH_WATERMARK | The watermark (background) bitmap style should be used. |
| PSH_USEHBMWATERMARK | Used with PSH_WATERMARK to specify that the hbmWatermark bitmap handle is used instead of using the pszbmWatermark string pointer to specify the watermark bitmap. |
| PSH_USEPLWATERMARK | Uses the supplied hplWatermark palette handle rather than the default palette. |
| PSH_STRETCHWATERMARK | By default, the background image is tiled over the property sheet area. You can set this flag to stretch the bitmap to cover the property sheet. |
| PSH_HEADER | You should set this flag to use a bitmap in the property sheet header section. |
| PSH_USEHBMHEADER | Used with PSH_HEADER to specify that the hbmHeader bitmap handle is used instead of using the pszbmHeader string pointer to specify the header bitmap. |
| PSH_USEPAGELANG | You should set this flag if you want the property sheet language settings to inherit from the first property page's resource template. |
The new CPropertyPageEx lets you specify both a title and subtitle for each property page, as well as the normal caption. The CPropertyPageEx class extends CPropertyPage and just supplies constructor functions to support the new features.
The CPropertyPageEx constructor lets you specify a resource dialog box template ID, a string resource ID for a caption, another ID for the header title, and an ID for a subtitle. You can let all but the first parameter (the dialog box template) default to zero if you don't need specific features.
As you'd expect by now, there is a corresponding Construct() function with identical parameters to allow for arrays of property pages.
You can access the m_psp PROPSHEETPAGE structure member to add some new flag values to the property page structure's dwFlags member. These flags are used with the Wizard97 property page style to customize the appearance of each property page. Table 7.3 lists the new flag values, which can be combined with a logical OR.
Table 7.3. Flag Values for Customizing the Wizard97 Property Page
| Flag Value | Description |
| PSP_HIDEHEADER | Hides the header section of the property page (when in Wizard97 mode). The property sheet background bitmap then is used to fill the entire property sheet. |
| PSP_USEHEADERTITLE | Displays the header text line. |
| PSP_USEHEADERSUBTITLE | Displays the subtitle line. |
The last two flags, PSP_USEHEADERTITLE and PSP_USEHEADERSUBTITLE, are set when you pass the title and subtitle resource IDs to the property sheet constructor function. However, you may want to set the PSP_HIDEHEADER flag to display a banner page in the property sheet's background watermark bitmap.
The following code lines show how two bitmap resources can be loaded and then displayed in the body and header sections of the property sheet (note that CBitmap pointers are cast into HBITMAP handles by their HBITMAP overloaded operator). Two property pages are constructed and added to the property sheet; the only difference is that one has the PSP_HIDEHEADER flag set. To simplify the code, only the base classes are used. In a real piece of code, however, you probably would want to derive your own classes from the CPropertySheetEx and CPropertyPageEx classes.
CBitmap bmHeader;
CBitmap bmBackground;
bmHeader.LoadBitmap(IDB_HEADER);
bmBackground.LoadBitmap(IDB_BACKGROUND);
CPropertySheetEx pex("New Property Sheet",
NULL,0,bmBackground,NULL,bmHeader);
CPropertyPageEx* pNewPage =
new CPropertyPageEx(IDD_PROPPAGE_FIRST,
IDS_PAGE_CAPTION1,IDS_HEADER_TITLE1,
IDS_SUBTITLE1);
pNewPage->m_psp.dwFlags |= PSP_HIDEHEADER;
pex.AddPage(pNewPage);
CPropertyPageEx* pNewPage1 =
new CPropertyPageEx(IDD_PROPPAGE_FIRST,
IDS_PAGE_CAPTION1,IDS_HEADER_TITLE1,
IDS_SUBTITLE1);
pex.AddPage(pNewPage1);
pex.m_psh.dwFlags |= PSH_WIZARD97;
pex.DoModal();
When run, this code displays the first property page without the header, with the property sheet background bitmap (IDB_BACKGROUND) filling the wizard dialog box, as Figure 7.9 shows.
Figure 7.9 A Wizard97 property page with the header hidden.
The second page is displayed with the new header style, showing the caption, header line, and subtitle line (see Figure 7.10).
Figure 7.10 A Wizard97 property page showing the header and subtitle lines.
Adding Help Buttons and Help Support
You can add a Help button to a property sheet by adding the PSP_HASHELP flag value to each property page that will support help. You should combine the PSP_HASHELP flag with the current dwFlags set in the page's m_psp PROPSHEETPAGE structure before displaying the property sheet. For example, you could add a line like this to the property page constructor:
m_psp.dwFlags |= PSP_HASHELP;
The Help button then is added to the property sheet and is enabled for each page that sets the PSP_HASHELP flag; otherwise, the button is disabled.
Alternatively, if you are dynamically adding pages after the property sheet has been dis played, you can ensure that the Help button is added by setting the PSH_HASHELP flag in the property sheet's m_psh.dwFlags member. The Help button then will be displayed when the property sheet window is created, even though no current property pages implement help support.
After you add the Help button, you can add a handler for the ID_HELP command message notification sent whenever the user clicks the Help button. ClassWizard doesn't automate this command message handler, so you must add the message map and handler function manually:
BEGIN_MESSAGE_MAP(CMyPropertyPage,CPropertyPage)
ON_COMMAND(ID_HELP,OnHelp)
END_MESSAGE_MAP
void CMyPropertyPage::OnHelp()
{
AfxMessageBox(IDS_HELP_RESPONSE);
}
Instead of displaying the message box as shown here, you normally would invoke your application's helper application.
Users can access context-sensitive help from the property sheet by pressing the F1 key. This generates a WM_HELPINFO message that can be handled by an OnHelpInfo() handler added to the property sheet or one of the property pages.
If you want to make the Help button use the same handler function as the context-sensitive F1 help, you can add an OnHelp() handler function to the property sheet class and add code to send a WM_COMMANDHELP message to the active property page using SendMessage(), like this:
#include "afxpriv.h"
void CSampleSheet::OnHelp()
{
SendMessage(WM_COMMANDHELP);
}
When the active property sheet receives this message, its own OnHelpInfo() handler function is called in response to a WM_HELPINFO message generated in response to WM_COMMANDHELP. By using this technique, you don't have to implement an OnHelp() handler in each of the property pages, and the standard framework OnHelpInfo() handler automatically invokes your application's helper application.
Understanding the Win32 Sequence of Events | Next Section

Account Sign In
View your cart