Visual C++ 6 Unleashed

Visual C++ 6 Unleashed

By MICKEY WILLIAMS and David Bennett

Creating a Property Sheet

The MFC wrapper classes CPropertySheet and CPropertyPage simplify the process of creating and displaying a property sheet and its pages. The CPropertySheet sheet class is derived directly from the CWnd class, although it has a lot in common with a normal CDialog class with a DoModal() function orchestrating the property sheet's modal lifetime. However, the CPropertyPage class, used for creating each property page, is derived directly from CDialog and acts largely like a modeless dialog box would.

By deriving your own classes from these two MFC base classes, you can extend the specific functionality required for your application. Like dialog boxes, each property page needs a dialog box template resource to define the user interface and layout of controls for that page. Creating this template is usually the first step when building a property sheet, because you then can use ClassWizard to automatically create your derived classes based on those templates, as the following sections show.

Creating the Property Page Resources

There is little difference between dialog box template resources used in property pages and those used in normal dialog boxes. You can use the Resource Editor's Insert Resource dialog box to add a property page specific template by selecting the IDD_PROPPAGE_LARGE, IDD_PROPPAGE_MEDIUM, or IDD_PROPPAGE_SMALL standard option in the Resource Type list (see Figure 7.1).

07fig01.gif

Figure 7.1 Inserting a standard property page dialog box template.

After inserting the property page dialog box template, you can resize it as required. You should bear in mind, however, that the final size of the property sheet will be based on the largest property page it contains.

The initial difference from the usual dialog box template that you'll notice is that there are no OK or Cancel buttons; these buttons are added automatically to the property sheet and shouldn't exist on the individual pages. If you examine the Styles tab of the Dialog Properties dialog box for the new template, you'll notice that Style is set to Child and Border is set to Thin, as shown in Figure 7.2.

07fig02.gif

Figure 7.2 The property page template Style and Border settings.

The More Styles tab reveals that the Disabled style also should be set for a property page.

07fig03.gif

Figure 7.3 The property page template Disabled flag.

As with a normal dialog box, you should set the resource ID and the caption as appropriate to your application, as shown in Figure 7.4. Your property page caption appears on the tab for that specific page when the property sheet is displayed.

07fig04.gif

Figure 7.4 The property page template resource ID and caption.

You can add controls to the property page resource template in exactly the same way as you would an ordinary dialog box. You can repeat the same process for each property page you want to include in your property sheet. Each property page must be assigned a unique resource ID, because they are each individual dialog boxes in their own right.

Creating CPropertyPage-Derived Classes

For each property page you want to display, you need to create a class derived from the CPropertyPage base class to implement your application-specific functionality. You can use ClassWizard to create this class quickly and easily, just as you would for a normal dialog box. If you invoke ClassWizard with the dialog box template resource selected, ClassWizard detects the new resource and offers the option of creating a new class or selecting from an existing implementation. If you decide to create a new class, the New Class dialog box then lets you specify a specific name for your new class. You must remember to change the Base Class combo box from the default CDialog setting to CPropertyPage, as shown in Figure 7.5.

07fig05.gif

Figure 7.5 Creating a CpropertyPage-derived handler class for the property page.

You'll notice that the new CPropertyPage-derived class is very similar to a direct CDialog derivation. The ID of the dialog box template resource is enumerated as the familiar IDD value in the class definition like this:

enum { IDD = IDD_PROPPAGE_FIRST };

The ID then is passed down into the CPropertyPage base class by the constructor function like this (excluding the ClassWizard lines):

CFirstPropertyPage::CFirstPropertyPage() :
            CPropertyPage(CFirstPropertyPage::IDD)
{
}

You'll also notice that, like a dialog box, the property page has a DoDataExchange() func tion so that you can exchange data between the controls and mapped member variables with the usual DDX macro entries. You also can validate that data using the normal DDV macros, which you easily can generate by using the ClassWizard Member Variables tab.

A message map lets you handle messages from dialog controls and notifications from the parent tab control and property sheet. Again, these message map entries have the usual ClassWizard comment placeholders to let you add handler functions from the Message Maps tab.

At this point, you can add mapping variables and event handlers for the various property page controls in the same way as you would for a dialog box. You can repeat the same process of adding a property page handler class for each of the property pages in your property sheet as if they were individual dialog boxes.

Creating a CPropertySheet-Derived Class

After you create a CPropertyPage-derived class to handle each page in the property sheet, you can create a new class to extend the CPropertySheet functionality to cope with your application-specific purpose and tie together the individual property pages.

It isn't strictly necessary to derive a new class from CPropertySheet, because the CPropertySheet base class has all of the functionality needed to display and tie together the various properties pages. For most applications, however, the various pages probably will maintain different attributes of the same object. The Resource Editor's Dialog Properties property sheet describes dialog box attributes, for example. Therefore, you'll probably want to embed the target object (or a pointer to it) inside the property sheet class. That way, each of the property pages can access the same target object via their parent property sheet.

Again, you can use ClassWizard to generate the CPropertySheet class just by setting the Base class control to CPropertySheet and entering a specific name for your new class.

If you take a look at the new class definition, you'll see that it includes a couple of very similar constructor functions and a message map. The two constructors are defined like this:

CSampleSheet(UINT nIDCaption, CWnd* pParentWnd = NULL,
                                   UINT iSelectPage = 0);
CSampleSheet(LPCTSTR pszCaption, CWnd* pParentWnd = NULL,

                                   UINT iSelectPage = 0);

The only difference between the two constructors is that the first needs a resource for a string table resource, but the second takes a pointer to a string to specify the required caption. By overriding the default zero value in the iSelectPage parameter, you can make a property page other than the first display initially.

Now that you have a CPropertySheet-derived class and some CPropertyPage-derived classes, you have all the elements needed to construct and display a property page object.

Adding the Property Pages

After constructing a CPropertySheet-derived object, you can use its AddPage() function to add the CPropertyPage-derived objects. After the pages are added, the whole sheet can be displayed like a modal dialog box by calling the CPropertySheet's DoModal() function, as in this example:

#include "FirstPropertyPage.h"
#include "SecondPropertyPage.h"
// ...
void CPropSheetSampleDoc::OnPropertysheetDisplay()
{
    CSampleSheet psSample("Sample Property Sheet");
    psSample.AddPage(new CFirstPropertyPage);
    psSample.AddPage(new CSecondPropertyPage);
    if (psSample.DoModal()==IDOK)
    {
        AfxMessageBox("Retrieve data from property sheet");
    }
    delete psSample.GetPage(0);
    delete psSample.GetPage(1);
}

After the sample property sheet is constructed, two property page objects are instantiated and added. The DoModal() call returns IDOK if the OK button was clicked just as it would for a dialog box.

You'll also notice that the GetPage() function is used to return the pointer to the property page objects.

Adding the pages outside the property sheet gives you some flexibility as to which pages should be added. However, you could call AddPage() to add the property pages inside the property sheet's constructor function (as embedded objects). This solution may be more elegant for situations where the pages used in the property are always the same and the property sheet is instantiated in several places.

In this situation, you can embed the property sheet objects in your derived CPropertySheet class, like this class-definition fragment:

protected:
    CFirstPropertyPage m_pageFirst;
    CSecondPropertyPage m_pageSecond;

Your property sheet constructor would be modified to add the two member property pages:

CSampleSheet::CSampleSheet(LPCTSTR pszCaption,
     CWnd* pParentWnd, UINT iSelectPage)
    :CPropertySheet(pszCaption, pParentWnd, iSelectPage)
{
    AddPage(&m_pageFirst);
    AddPage(&m_pageSecond);
}

The greatly simplified code required to display the property sheet now is like this:

CSampleSheet psSample("Sample Property Sheet");
if (psSample.DoModal()==IDOK) AfxMessageBox("Ok");

If you need to create an array of property sheets, you can break the construction process down into two steps by using the Construct() function, which has parameters identical to the constructor. For example, the following lines create two property sheets and then set the caption in two stages. Afterward, you can call DoModal() to display the property sheets as normal:

CSampleSheet arpsSample[2];
arpsSample.Construct("Sheet 1");
arpsSample.Construct("Sheet 2");

Creating a Modeless Property Sheet

You can create a modeless property sheet simply by calling the CPropertySheet's Create() function rather than its DoModal() function. A modeless property sheet is created without the OK, Cancel, and Apply buttons, but it may be closed using the sheet's system menu or close button. If you need these buttons on a modeless dialog box, you'll need to override the CPropertySheet's OnCreate() function and add the buttons manually.

Another consideration is the scope of the property sheet object. Unlike a modal property sheet, your property sheet can't be declared as a local variable to a function. This is because the Create() function returns immediately, leaving the modeless property sheet open for use. If it were defined as a local variable, the property sheet object would be destroyed as soon as the invoking function returns.

So as with modeless dialog boxes, a modeless property sheet object should be created with the C++ new operator and tracked by a pointer embedded in a holding class responsible for deleting the allocated property sheet object after its window is closed.

Alternatively, you may want to make the property sheet responsible for its own tracking by overriding the PostNcDestroy() virtual function (called when the property sheet window is being closed) and delete the C++ this pointer to free the property sheet memory.

You can force a modeless property sheet window to close from within your code by calling its DestroyWindow() function.

The following code fragment illustrates these concepts:

CPropSheetSampleDoc::CPropSheetSampleDoc() : m_pPSSample(NULL)
{
}

CPropSheetSampleDoc::~CPropSheetSampleDoc()
{
    if (m_pPSSample) delete m_pPSSample;
}
void CPropSheetSampleDoc::OnPropertysheetDisplay()
{
    if (m_pPSSample==NULL)
    {
        m_pPSSample = new CSampleSheet("Modeless Property Sheet");
        m_pPSSample->Create(AfxGetMainWnd());
    }
}

void CPropSheetSampleDoc::OnPropertysheetClose()
{
    if (m_pPSSample)
    {
        m_pPSSample->DestroyWindow();
        delete m_pPSSample;
        m_pPSSample = NULL;
    }
}

This code assumes that the property pages are created inside the property sheet's constructor and that the m_pPSSample property sheet pointer is declared in the document class definition like this:

CSampleSheet* m_pPSSample;

The OnPropertysheetDisplay() function is a menu handler function that allows the user to create the modeless property sheet. The modeless property sheet is created as a child of the application's main window by the Create() call if the object doesn't already exist.

The OnPropertysheetClose() function is another menu handler function that demonstrates how the property sheet can be closed from within the code with a call to DestroyWindow().

Share ThisShare This

Informit Network