One of the newest types of graphical objects is the tabbed dialog box, also known as a property sheet. Windows 95 is loaded with property sheets, which organize the many options that can be modified by the user. What's a property sheet? Basically, it's a dialog box with two or more pages. You flip the pages by clicking labeled tabs located at the top of the dialog box. By using such dialog boxes to organize complex groups of options, Windows 95 enables users to find more easily the information and settings that they need. As you've probably guessed, Visual C++ 5.0 supports the Windows 95 property sheets, with the classes CPropertySheet and CPropertyPage.
Similar to property sheets are wizards, which use buttons to move from one page to another rather than using tabs. You've seen a lot of wizards, too. These special types of dialog boxes guide the user step-by-step through complicated processes. For example, when you use AppWizard to generate source code for a new project, the wizard guides you through the entire process. To control the wizard, you click buttons labeled Back, Next, and Finish.
Finding a sample property sheet in Windows 95 is as easy as finding sand at the beach. Just click virtually any Properties command or double-click an icon in the Control Panel. For example, Figure 9.1 shows the dialog box that you see when you double-click the Control Panel's Add/Remove Programs icon. This is a property sheet that contains three pages labeled Install/Uninstall, Windows Setup, and Startup Disk, each page containing commands and options related to the page's title topic.
Figure 9.1 : The Add/Remove Programs Properties sheet contains three tabbed pages.
In Figure 9.1, you can see programs installed on the machine that Windows can automatically uninstall. There's also an Install button that leads to other dialog boxes that help you install new programs from floppy disk or CD-ROM. On the other hand, the Windows Setup page (Figure 9.2) helps you add or remove files from the Windows system. To get to this page, you need only click the Windows Setup tab. The Startup Disk page, of course, houses yet another set of options.
Figure 9.2 : To move to the Windows Setup page, you click the Windows Setup tab.
As you can see, property sheets are a great way to organize many types of related options. Gone are the days of dialog boxes so jam-packed with options that you needed a college-level course just to figure them out. In the sections that follow, you will learn to program your own tabbed property sheets using MFC's CPropertySheet and CPropertyPage classes.
Now that you've had an introduction to property sheets, it's time to learn how to build an application that uses these handy specialized dialog boxes. In the steps that come later, you'll build the Property Sheet Demo application, which demonstrates the creation and manipulation of property sheets. Follow the steps that come next to create the basic application and modify its resources.
NOTE |
The complete source code and executable file for the Property Sheet application can be found in the CHAP09\PSHT directory of this book's CD-ROM. |
Dialog Box Name | Options to Select |
New Project | Name the project psht and then set the project path to the directory into which you want to store the project's files. Leave the other options set to their defaults. |
Step 1 | Select Single Document. |
Step 2 of 6 | Leave set to defaults. |
Step 3 of 6 | Leave set to defaults. |
Step 4 of 6 | Turn off all application features. |
Step 5 of 6 | Leave set to defaults. |
Step 6 of 6 | Leave set to defaults. |
Now that you have the application's basic resources the way you want them, it's time to add the resources that define the application's property sheet. This means creating dialog box resources for each page in the property sheet. Follow the next steps to complete this task.
You now have all your resources created. However, you need to associate your two new property-page resources with C++ classes so that you can control them in your program.You also need a class for your property sheet, which will hold the property pages that you've created. Follow the steps given next to create the new classes.
.
At this point, you have three new classes-CPage1, CPage2, and CPropSht-in your program. The first two classes are derived from MFC's CPropertyPage class, and the third is derived from CPropertySheet. Although ClassWizard has created the basic source-code files for these new classes, you still have to add code to the classes to make them work the way you want. Follow the next set of steps to complete the Property Sheet Demo application.
Listing 9.1 LST9_1.TXT-Code for the OnDraw() Function
pDC->TextOut(20, 20, m_edit); if (m_check) pDC->TextOut(20, 50, "TRUE"); else pDC->TextOut(20, 50, "FALSE");
Listing 9.2 LST9_2.TXT-Code for the OnPropSheet() Function
CPropSht propSheet("Property Sheet", this, 0); propSheet.m_page1.m_edit = m_edit; propSheet.m_page2.m_check = m_check; int result = propSheet.DoModal(); if (result == IDOK) { m_edit = propSheet.m_page1.m_edit; m_check = propSheet.m_page2.m_check; Invalidate(); }
You've now finished the complete application. Click the toolbar's Build button, or select the Build command from the menu bar, to compile and link the application.
Once you have the program compiled, run it. When you do, you see
the window shown in Figure 9.16. As you can see, the window displays
two values, which are the default values for the controls in the
application's property sheet. You can change these values using
the property sheet. To do this, select the File menu's Property
Sheet command. The property sheet appears on the screen (Figure
9.17). The property sheet contains two pages, each of which holds
a single control. When you change the settings of these controls
and click the property sheet's OK button, the application's window
displays the new values.
Figure 9.16 : When it first starts, the Property Sheet Demo application displays default values for the property sheet's controls.
Figure 9.17 : The application's property sheet contains two pages.
Previously, in the section titled "Creating the Property Sheet Demo Application," you went through the process of creating Property Sheet Demo, step-by-step. During this process, you discovered that you must complete several tasks in order to add property sheets to your application. Each of those steps was explained in the step's text. However, to give you a clearer picture of what you did, the steps that are most important in the creation of a property sheet are summarized here:
NOTE |
As you read over the steps required for creating a property sheet, be sure that you understand the difference between a property sheet and a property page. A property sheet is a window that contains property pages. Property pages are windows that hold the controls that will appear on the property sheet's pages. |
After you have your application written and have defined the resources and classes that represent the property sheet (or sheets; you can have more than one), you need a way to enable the user to display the property sheet when it's needed. In Property Sheet Demo, this is done by associating a menu item with a message-response function. However you handle the command to display the property sheet, though, the process of creating the property sheet is the same. First, you must call the property sheet class's constructor, which Property Sheet Demo does like this:
CPropSht propSheet("Property Sheet", this, 0);
Here, the program is creating an instance of the CPropSht class. This instance (or object) is called propSheet. The three arguments are the property sheet's title string, a pointer to the parent window (which, in this case, is the view window), and the zero-based index of the first page to display. Because the property pages are created in the property sheet's constructor, creating the property sheet also creates the property pages.
Once you have the property sheet object created, you can initialize the data members that hold the values of the property page's controls, which Property Sheet Demo does like this:
propSheet.m_page1.m_edit = m_edit; propSheet.m_page2.m_check = m_check;
Now it's time to display the property sheet on the screen, which you do just as if it were a dialog box, by calling the property sheet's DoModal() member function:
int result = propSheet.DoModal();
DoModal() doesn't take any arguments, but it does return a value indicating which button the user clicked to exit the property sheet. In the case of a property sheet or dialog box, you'll usually want to process the information entered into the controls only if the user clicked the OK button, which is indicated by a return value of IDOK. Listing 9.3 shows how the Property Sheet Demo application handles the return value:
Listing 9.3 LST9_3.TXT-Handling the Property Sheet's Return Value
if (result == IDOK) { m_edit = propSheet.m_page1.m_edit; m_check = propSheet.m_page2.m_check; Invalidate(); }
In Listing 9.3, the program retrieves the values of the controls from the property pages and then calls Invalidate() to force the window to be redrawn. If the user exits the property sheet by clicking the Cancel button, the code in the body of the if statement is ignored and the window is not updated.
When you come right down to it, a wizard is nothing more than a property sheet that uses Back, Next, and Finish buttons instead of tabs. Because of the lack of tabs, however, the user must switch from one page to another in sequence. This forced sequence makes wizards terrific for guiding your application's users through the steps needed to complete a complex task. You've already seen how AppWizard in Visual C++ makes it easy to start a new project. You can create your own wizards that are suited to whatever application you want to build. In the following sections, you'll see how easy it is to convert a property sheet to a wizard.
In the CHAP09\WIZ folder of this book's CD-ROM, you'll find the Wizard Demo application. This application was built in much the same way as the Property Sheet Demo application that you created earlier in this chapter. However, as you'll soon see, there are a few differences in the Wizard Demo application that enable the user to access and use the application's wizard.
When you run the Wizard Demo application, the main window appears,
including a File menu from which you can select the Wizard command.
The Wizard command brings up the wizard shown in Figure 9.18.
Figure 9.18 : The Wizard Demo application displays a wizard rather than a property sheet.
The wizard isn't too fancy, but it does demonstrate what you need to know in order to program more complex wizards. As you can see, this wizard has three pages. On the first page is an edit control and three buttons called Back, Next, and Cancel. The Back button is disabled, because there is no previous page to go back to. The Cancel button enables the user to dismiss the wizard at any time, canceling whatever process the wizard was guiding the user through. The Next button causes the next page in the wizard to be displayed.
You can change whatever is displayed in the edit control if you
like. However, the magic really starts when you click the Next
button, which displays Page 2 of the wizard, as shown in Figure
9.19. Page 2 contains a check box and the Back, Next, and Cancel
buttons. Now, the Back button is enabled, so that you can return
to Page 1 if you want to. Go ahead and click the Back button.
The wizard tells you that the check box must be checked, as shown
in Figure 9.20. As you'll soon see, this feature of a wizard enables
you to verify the contents of a specific page before allowing
the user to advance to another step.
Figure 9.19 : In Page 2 of the wizard, the Back button is enabled.
Figure 9.20 : You must select the check box before the wizard will let you leave Page 2.
After checking the check box, you can click the Back button to move back to Page 1 or click the Next button to advance to Page 3. Assuming you advance to Page 3, you see the display shown in Figure 9.21. Here, the Next button has changed to the Finish button, because you are on the wizard's last page. If you click the Finish button, the program displays a message box, after which the wizard disappears.
Figure 9.21 : This is the last page of the Wizard Demo Application's wizard.
As far as your application's resources go, you create wizard pages exactly as you create property sheet pages, by creating dialog boxes and changing the dialog box styles. You also need to associate each page that you create with an object of the CPropertyPage class. However, in order to take control of the pages in your wizard and keep track of what the user is doing with the wizard, there are a couple of member functions in the CPropertyPage class that you can override in your property page classes. These functions are OnSetActive(), OnWizardBack(), OnWizardNext(), and OnWizardFinish(). Read on to see how to use these functions.
MFC automatically calls the OnSetActive() member function immediately upon displaying a specific page of the wizard. For example, when the program displays Page 1 of the wizard, the CPage1 class's OnSetActive() function gets called. In the Wizard Demo application, the CPage1 class's version of OnSetActive() looks like Listing 9.4.
Listing 9.4 LST9_4.TXT-The OnSetActive() Member Function
BOOL CPage1::OnSetActive() { // TODO: Add your specialized code here and/or call the base class CPropertySheet* parent = (CPropertySheet*)GetParent(); parent->SetWizardButtons(PSWIZB_NEXT); return CPropertyPage::OnSetActive(); }
In Listing 9.4, the program first gets a pointer to the wizard's property sheet window, which is the page's parent window. Then the program calls the wizard's SetWizardButtons() function, which determines the state of the wizard's buttons. SetWizardButtons() takes a single argument, which is a set of flags indicating how the page should display its buttons. These flags are PSWIZB_BACK, PSWIZB_NEXT, PSWIZB_FINISH, and PSWIZB_DISABLEDFINISH. The button flags that you include will enable the associated button (except for the PSWIZB_DISABLEDFINISH flag, which disables the Finish button). Because the call to SetWizardButtons() in Listing 9.4 includes only the PSWIZB_NEXT flag, only the Next button in the page will be enabled.
Because the CPage2 class represents Page 2 of the wizard, its call to SetWizardButtons() enables both the Back and Next buttons, by ORing together the appropriate flags, like this:
parent->SetWizardButtons(PSWIZB_BACK | PSWIZB_NEXT);
Because Page 3 of the wizard is the last page, the CPage3 class calls SetWizardButtons(), like this:
parent->SetWizardButtons(PSWIZB_BACK | PSWIZB_FINISH);
This set of flags enables the Back button and changes the Next button to the Finish button.
In the simplest case, MFC takes care of everything that needs to be done in order to flip from one wizard page to the next. That is, when the user clicks a button, MFC springs into action and performs the Back, Next, Finish, or Cancel command. However, you'll often want to perform some action of your own when the user clicks a button. For example, you may want to verify that the information that the user entered into the currently displayed page is correct. If there's a problem with the data, you can force the user to fix it before moving on.
To respond to the wizard's buttons, you can override the OnWizardBack(), OnWizardNext(), and OnWizardFinish() member functions. When the user clicks a wizard button, MFC calls the matching function in which you can do whatever is needed to process that page. An example is the way the wizard in the Wizard Demo application won't let you leave Page 2 until you've checked the check box. This is accomplished by overriding the functions shown in Listing 9.5.
Listing 9.5 LST9_5.TXT-Responding to Wizard Buttons
LRESULT CPage2::OnWizardBack() { // TODO: Add your specialized code here and/or call the base class CButton *checkBox = (CButton*)GetDlgItem(IDC_CHECK1); if (!checkBox->GetCheck()) { MessageBox("You must check the box."); return -1; } return CPropertyPage::OnWizardBack(); } LRESULT CPage2::OnWizardNext() { // TODO: Add your specialized code here and/or call the base class CButton *checkBox = (CButton*)GetDlgItem(IDC_CHECK1); if (!checkBox->GetCheck()) { MessageBox("You must check the box."); return -1; } return CPropertyPage::OnWizardNext(); }
In the functions in Listing 9.5, the program gets a pointer to the page's check box by calling the GetDlgItem() function. With the pointer in hand, the program can call the check-box class's GetCheck() function, which returns a 1 if the check box is checked. If GetCheck() returns a 0, the program displays a message box and returns Ð1 from the function. Returning Ð1 tells MFC to ignore the button click and not change pages.
As you've just learned, almost all the work involved in controlling a wizard is done in the classes that represent the wizard's pages. The property sheet class that represents the wizard works exactly the same as it did in the property sheet example. However, there is one extra thing you must do when displaying a wizard, which is to call the property sheet's SetWizardMode() member function. This function call tells MFC that it should display the property sheet as a wizard rather than as a conventional property sheet. Listing 9.6 shows the view class's OnWizard() member function, which is the function that responds to the File menu's Wizard command.
Listing 9.6 LST9_6.TXT-Displaying a Property Sheet as a Wizard
void CWizView::OnWizard() { // TODO: Add your command handler code here CWizSheet wizSheet("Sample Wizard", this, 0); wizSheet.m_page1.m_edit = m_edit; wizSheet.m_page2.m_check = m_check; wizSheet.SetWizardMode(); int result = wizSheet.DoModal(); if (result == ID_WIZFINISH) { m_edit = wizSheet.m_page1.m_edit; m_check = wizSheet.m_page2.m_check; } }
Notice in Listing 9.6 that the program creates the wizard almost exactly the same as a property sheet, the only difference being the call to SetWizardMode(). The wizard is displayed exactly the same as any other dialog box, by calling the object's DoModal() member function. There is, however, one difference in how you respond to the result returned by DoModal(). Because a wizard has no OK button, DoModal() cannot return IDOK. Instead, DoModal() returns ID_WIZFINISH if the user exits via the Finish button.
Whether you're creating property sheets or wizards, Visual C++'s many classes enable you to get the job done easily. Property sheets are great for organizing many options and controls, whereas wizards (which are a special type of property sheet) are best used for guiding the user step-by-step through a complex task. To learn more about related topics, check out the following chapters: