One of the newest types of graphical objects is the tabbed dialog box, also known as a property sheet. A property sheet is a dialog box with two or more pages. Windows and NT are loaded with property sheets, which organize the many options that users can modify. You flip the pages by clicking labeled tabs at the top of the dialog box. Using such dialog boxes to organize complex groups of options enables users to more easily find the information and settings they need. As you've probably guessed, Visual C++ 6 supports property sheets, with the classes CPropertySheet and CPropertyPage.
Similar to property sheets are wizards, which use buttons instead of tabs to move from one page to another. You've seen a lot of wizards, too. These special types of dialog boxes guide users 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 is as easy as finding sand at the beach. Just click virtually any Properties command or bring up an Options dialog in most applications. For example, Figure 12.1 shows the dialog box that you see when you choose Tools, Options from within Visual C++. This property sheet contains 12 pages in all, each covering a different set of options.
FIG. 12.1 The Options properties sheet contains many tabbed pages.
NOTE: Many people forget 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 controls. They appear on the property sheet.
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 following sections, you'll learn to program your own tabbed property sheets by 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. You're about to build the Property Sheet Demo application, which demonstrates the creation and manipulation of property sheets. Follow the steps in the following sections to create the basic application and modify its resources.
First, use AppWizard to create the basic files for the Property Sheet Demo program, selecting the options listed in the following table. When you're done, the New Project Information dialog box appears; it will look like Figure 12.2. Click OK to create the project files.
Dialog Box Name | Options to Select |
New, Project tab | Name the project Propsheet and then set the project path to the directory in which you want to store the project's files. Make sure that MFC AppWizard (exe) is highlighted. 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. |
FIG. 12.2 Your New Project Information dialog box looks like this.
Now you'll edit the resources in the application generated for you by AppWizard, removing unwanted menus and accelerators, editing the About box, and most importantly, adding a menu item that will bring up a property sheet. Follow these steps:
FIG. 12.3 The ResourceView tab displays the ResourceView window.
FIG. 12.4 Add a Property Sheet command to the File menu.
FIG. 12.5 The About box looks like this.
FIG. 12.6 The first segment of the IDR_MAINFRAME string appears in your main window's title bar.
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 these steps:
FIG. 12.7 Change the caption and resource ID of the new dialog box.
FIG. 12.8 A property page uses styles different from those used in regular dialog boxes.
FIG. 12.9 A property page can hold whatever controls you like.
FIG. 12.10 The second property page looks like this.
You now have all your resources created. Next, 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 these steps to create the new classes:
FIG 12.11 ClassWizard makes it easy to connect controls on a dialog box to member variables of the class representing the dialog box.
FIG. 12.12 The second property page needs a Boolean member variable called m_checkbox.
At this point, you've done all the resource editing and don't need to have so many windows open. Choose Window, Close All from the menu bar and close the properties box. You'll now create a property sheet class that displays the property pages already created. Follow these steps:
Mow you have three new classes--CPage1, CPage2, and CPropSheet--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 these steps to complete the Property Sheet Demo application:
#include "page1.h" #include "page2.h"
FIG. 12.13 The ClassView window lists the classes that make up your project.
CPage1 m_page1; CPage2 m_page2;
AddPage(&m_page1); AddPage(&m_page2);
protected: CString m_edit; BOOL m_check;
m_edit = "Default"; m_check = FALSE;
void CPropsheetView::OnDraw(CDC* pDC) { CPropsheetDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); pDC->TextOut(20, 20, m_edit); if (m_check) pDC->TextOut(20, 50, "TRUE"); else pDC->TextOut(20, 50, "FALSE");
}
#include "propsheet1.h"
FIG. 12.14 Use ClassWizard to add the OnPropsheet() member function.
void CPropsheetView::OnPropsheet() { CPropSheet propSheet("Property Sheet", this, 0); propSheet.m_page1.m_edit = m_edit; propSheet.m_page2.m_checkbox = m_check; int result = propSheet.DoModal(); if (result == IDOK) { m_edit = propSheet.m_page1.m_edit; m_check = propSheet.m_page2.m_checkbox; Invalidate(); }
}
You've finished the complete application. Click the Build button on the Build minibar (or choose Build, Build) to compile and link the application. Run it by choosing Build, Execute or by clicking the Execute button on the Build minibar. When you do, you see the window shown in Figure 12.15.
As you can see, the window displays two values--the default values for the controls in the application's property sheet. You can change these values by using the property sheet. Choose File, Property Sheet; the property sheet appears (see Figure 12.16). 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. Try it!
FIG. 12.15 When it first starts, the Property Sheet Demo application displays default values for the property sheet's controls.
FIG. 12.16 The application's property sheet contains two pages.
To add a property sheet to one of your own applications, you follow steps very similar to those you followed in the previous section to create the demo application:
After you write your application and define the resources and classes that represent the property sheet (or sheets--you can have more than one), you need a way to enable users 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, 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:
CPropSheet propSheet("Property Sheet", this, 0);
Here, the program creates an instance of the CPropSheet 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.
After you create the property sheet object, 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_checkbox = m_check;
Now it's time to display the property sheet, which you do just as though 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 users clicked to exit the property sheet. In a property sheet or dialog box, you'll usually want to process the information entered into the controls only if users clicked OK, which is indicated by a return value of IDOK. If users exit the property sheet by clicking the Cancel button, the changes are ignored and the view or document member variables aren't updated.
Here's a piece of information that surprises most people: A wizard is just a special property sheet. Instead of tabbed pages on each sheet that allow users to fill in the information in any order or to skip certain pages entirely, a wizard has Back, Next, and Finish buttons to move users through a process in a certain order. 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 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.
To understand Wizards, this section will show you the Wizard Demo application, which is built in much the same way as the Property Sheet Demo application that you created earlier in this chapter. This chapter won't present step-by-step instructions to build Wizard Demo. You will be able to build it yourself if you want, using the general steps presented earlier and the code snippets shown here.
When you run the Wizard Demo application, the main window appears, looking very much like the Property Sheet Demo main window. The File menu now includes a Wizard item; choosing File Wizard brings up the wizard shown in Figure 12.17.
FIG. 12.17 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 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: Back, Next, and Cancel. The Back button is disabled because there's no previous page to go back to. The Cancel button enables users to dismiss the wizard at any time, canceling whatever process the wizard was guiding users through. The Next button causes the next page in the wizard to appear.
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 (see Figure 12.18). Page 2 contains a check box and the Back, Next, and Cancel buttons. Now the Back button is enabled, so 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 (see Figure 12.19). As you'll soon see, this feature of a wizard enables you to verify the contents of a specific page before allowing users to advance to another step.
FIG. 12.18 In Page 2 of the wizard, the Back button is enabled.
After checking the check box, you can click the Back button to move back to Page 1 or click Next to advance to Page 3. Assuming that you advance to Page 3, you see the display shown in Figure 12.20. 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 wizard disappears.
FIG. 12.19 You must select the check box before the wizard will let you leave Page 2.
FIG. 12.20 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. The dialog titles--Page 1 of 3, Page 2 of 3, and Page 3 of 3--are hardcoded onto each dialog box. You associate each dialog box resource with an object of the CPropertyPage class. Then, to take control of the pages in your wizard and keep track of what users are doing with the wizard, you override the OnSetActive(), OnWizardBack(), OnWizardNext(), and OnWizardFinish() functions of your property page classes. Read on to see how to do this.
The File, Wizard command is caught by CWizView's OnFileWizard() function. It's very similar to the OnPropSheet() function in the Property Sheet demo, as you can see from Listing 12.3. The first difference is the call to SetWizardMode() before the call to DoModal(). This function call tells MFC that it should display the property sheet as a wizard rather than as a conventional property sheet. The only other difference is that users arrange for property sheet changes to be accepted by clicking Finish, not OK, so this code checks for ID_WIZFINISH rather than IDOK as a return from DoModal().
void CWizView::OnFileWizard() { 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; Invalidate(); }
}
MFC automatically calls the OnSetActive() member function immediately upon displaying a specific page of the wizard. So, when the program displays Page 1 of the wizard, the CPage1 class's OnSetActive() function is called. You add code to this function that makes the wizard behave as you want. CPage1::OnSetActive() looks like Listing 12.4.
BOOL CPage1::OnSetActive() { CPropertySheet* parent = (CPropertySheet*)GetParent(); parent->SetWizardButtons(PSWIZB_NEXT); return CPropertyPage::OnSetActive();
}
OnSetActive() 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. Because the call to SetWizardButtons() in Listing 12.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 the Back and Next buttons by combining the appropriate flags with the bitwise OR operator (|), 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 provides a Finish button instead of a Next 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 users click 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 users click a button. For example, you may want to verify that the information that users entered into the currently displayed page is correct. If there's a problem with the data, you can force users to fix it before moving on.
To respond to the wizard's buttons, you override the OnWizardBack(), OnWizardNext(), and OnWizardFinish() member functions. Use the Message Maps tab of ClassWizard to do this; you'll find the names of these functions in the Messages window when a property page class is selected in the Class Name box. When users click a wizard button, MFC calls the matching function which does 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 12.5.
LRESULT CPage2::OnWizardBack() { CButton *checkBox = (CButton*)GetDlgItem(IDC_CHECK1); if (!checkBox->GetCheck()) { MessageBox("You must check the box."); return -1; } return CPropertyPage::OnWizardBack(); } LRESULT CPage2::OnWizardNext() { UpdateData(); if (!m_check) { MessageBox("You must check the box."); return -1; } return CPropertyPage::OnWizardNext();
}
These functions demonstrate two ways to examine the check box on Page 2. OnWizardBack() 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. OnWizardNext() calls UpdateData() to fill all the CPage2 member variables with values from the dialog box controls and then looks at m_check. In both functions, if the box isn't checked, 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 can see, it is simple to arrange for different conditions to leave the page in the Back or Next direction.
© Copyright, Macmillan Computer Publishing. All rights reserved.