Home

A Property Sheet-Based Application

 

Introduction

Microsoft Visual C++ 6.0 provides a quick means of creating property pages, using Project -> Add To Project -> Components and Controls... combination from the main menu, which opens the Components and Controls Gallery dialog box, allowing you to follow easy step-by-step instructions to create a property sheet or a wizard. As easy as this approach is, it has its own disadvantages.

Just as you can create a dialog-based application by selecting Dialog Based in the first step of AppWizard, you can create a property sheet-based application. That is, an application that presents only a property sheet and its property pages to the user. Unfortunately, AppWizard doesn't provide a wizard to do this but this is particularly easy by simply configuring the first or main dialog box of a dialog-based application created using AppWizard.

Project Preparation

To have an application whose main object is a property sheet, you can start by creating a dialog based application using AppWizard. In this case you should select the Dialog Based radio button on the first step of the wizard. Alternatively, you can work from scratch, which we will do here.

To create an application that uses property pages as its main objects, you can start a New Project:

  • If you are using MSVC 6, in the New dialog box and in the Projects property page, you can click Win32 Application and, in the Name edit box, type a name for the project, such as Census. Then click OK
  • If you are using MSVC .NET, in the New Project dialog box and in the Templates list, you can click Win32 Project, type a name in the Name edit box, such as Census and click OK

In both cases, you would be given a lot of options by the wizard. These options can tremendously reduce your work. Because we are actually creating an MFC application, you should elect to create a Windows Application Empty Project and Finish with the wizard.

Creating a Win32 Application may seem like a lot of work but its main draw back is that you know exactly what is included in, and what is absent from, your application because you would be deciding about every line of code and every resource used. Since this is an MFC application, you must tell the compiler that you will need MFC DLLs in your application. To do this:

  • If you are using MSVC 6, on the main menu, you can click Project -> Settings. Then, in the Project Settings dialog box, in the General tab and in the Microsoft Foundation Classes combo box, select either Use MFC In A Static DLL or Use MFC In A Shared DLL, and click OK
     

     
  • If you are using MSVC .NET, either in Class View or in Solution Explorer, you can right-click the name of the project and click Properties. In the Property Pages, after selecting the General node, in the Use MFC field, select either Use MFC In A Static DLL or Use MFC In A Shared DLL and click OK

 

Project Resources

A property sheet is like a control controls container. Its objects are mainly property pages. The MFC library treats a property page essentially as a dialog box although it is not one. Therefore, to create this type of application, you may first start by creating property pages. Because a property page closely resembles a dialog box, its resource is similar to that of a dialog box. Based on this, to create a property page, you can open the Add Resources dialog box (MSVC 6 => Insert -> Resources... MSVC .Net => Project -> Add Resource...). Microsoft Visual C++ makes it particularly easy to create a property page. To do this, in the Add or Insert Resource dialog box, you can expand the Dialog node and click either of IDD_PROPAGE_X variants:

After making your selection, you can click New. If you add selected the regular Dialog from the Add or Insert Resource dialog box, you can easily convert it into a property by setting the following properties:

  • Style: Child - This is needed because the property is in fact a child of the property sheet. The property page will be "embedded" in the property sheet
  • Border: Thin - Unlike a regular dialog box, a property page doesn't need borders and should not display borders. The user doesn't need to see where a property page starts and where it ends.
  • Title Bar: true - Because there can be various property pages on a property sheet, even if there is only one, a property page should display a general label that indicates what it is used for. That label is placed on the tab section of the property page, although the parent property sheet has its own title bar. To display a tab on a property page, you should give a title bar to its list of style
  • System Menu: false - Once again, a property page is in fact a child control. Since it doesn't have an actual title bar, it doesn't need to display a system or any system button

Like every resources of an MFC application, a property page must have an identifier. The identifier is set using the ID combo box in the Properties window. The identifier our first property page can be set as IDD_PERSINFO

Another important property of a control that displays text is its Caption. The caption displays as text on the tab of a property page. The caption of our first property page can be set to Personal Information.

Once a property page is created, as if it were a regular dialog box, you can add the necessary controls to it.

As done for the first property page, you can add others as you judge necessary. For this exercise, I add an IDD_PROPPAGE_LARGE resource IDentified as IDD_EDUCATION with a Caption of Education. I add another IDD_PROPPAGE_LARGE resource IDentified as IDD_SECURITYBACKGROUND with a Caption of Security Background.

After creating one or all of the resources of a Win32 application that serves as the basis of an MFC application, you should save it. This is particularly important in MSVC 6 because MSVC 6 doesn't create a resource file for you. MSVC .Net creates a resource script automatically as soon as a resource is added to a project that doesn't yet have a resource script. To create a resource script in MSVC 6, after creating one or more resources, on the Standard toolbar, you can click either the Save  or the Save All buttons (from my experience, a better approach to do this is to close the dialog boxes that you have created; you will be asked whether you want to save, in which case you would click Yes). You will need to provide a name. By tradition or pure good habits, the name of the resource script usually carries the same name as the project but this is only a suggestion. For this exercise, we will name our resource script as Census.rc. After specifying the name, you can click Save. Still in MSVC 6, you must explicitly add the script to your project. To do this, on the main menu, you can click Project -> Add To Project -> Files... Select the resource script file that has the rc extension and click OK.

Project Coding

As always, you have various options in coding your application. You can create a single file that would be used to programmatically manage the property pages. If you are creating a large application, such a file would soon be crowded. The alternative is to create a file for each property page. With this option, you can create a single file that contains the header and source code for a property page. This solution reduces the number of lines of  codes of a the earlier mentioned file but it doesn't take full advantage of C++' ability to separate the foundation from the implementation of an object. Therefore, for this lesson, we will create a header and a source files for each property page. This will provide for better management of objects because we can easily locate code for each object.

If an application is using various header files for its objects, it is a good idea to create a single header file that list them, and then include this file wherever the header files are needed. To do this, you can create a header file named StdAfx.h and create its source file named StdAfx.cpp. The StdAfx.cpp source file simply includes the name of its header file:

#include "StdAfx.h"

In an MFC application, every dialog box, which implies every property page must programmatically be represented by its own class. In ideal world, you should be able to right-click a dialog box or a property page, click Add Class, follow a wizard, and generate a class but there are some issues we must deal with. First, MSVC 6 and MSVC .Net have different mechanisms to create a class for a property page. Second, MSVC .Net cannot generate a class for the kinds of property pages of the current application. Therefore, we will do some things manually, and this will help us better control the content of our application. 

To create a class for a property page:

  • If you are using MSVC 6, on the main menu, you can click Insert -> New Class... In the New Class dialog box, you can type a class name in the Name edit box, such as CPersonalInfo. In the Derived From column, you can type CPropertyPage and click OK twice
     
  • If you are using MSVC 7, on the main menu, you can click Project -> Add Class... In the Add Class dialog box, you can click Generic C++ Class in the Templates and click Open. In the Generic C++ Class Wizard, you must type a name for the Class Name in the equivalent edit box. An example would be CPersonalInfo. In the Base Class edit box, you should type the name of the class on which your property page would be based. In this case this would be CPropertyPage. Then you can click Finish
     

After creating the class for a property page, you must configure it as valid. First, because the CPropertyPage class that is the ancestor to your property page class is defined in the afxdlgs.h header file, you should include it in each class that needs the class. We mentioned earlier that you can use a central file that lists the common header files used in your application. This StdAfx.h header file can be changed as follows:

#pragma once

#include <afxwin.h>
#include <afxdlgs.h>
#include <afxcmn.h>

To validate the class used to manage the property page, you can change it as follows (the following file is for MSVC 6; the first five lines of code are different for MSVC .Net but the result is the same)(we added the macros and some optional virtual methods that are most likely to be needed in your application):

// PersonalInfo.h: interface for the CPersonalInfo class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_PERSONALINFO_H__FAD39CF4_50EC_42C2_BD08_83A2C5430D43__INCLUDED_)
#define AFX_PERSONALINFO_H__FAD39CF4_50EC_42C2_BD08_83A2C5430D43__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include "resource.h"

class CPersonalInfo : public CPropertyPage  
{
	DECLARE_DYNCREATE(CPersonalInfo)

public:
	CPersonalInfo(CWnd *pParent = NULL);
	virtual ~CPersonalInfo();

	enum { IDD = IDD_PERSINFO };

protected:
	virtual void DoDataExchange(CDataExchange* pDX);
	virtual BOOL OnInitDialog();

	DECLARE_MESSAGE_MAP()
};

#endif // !defined(AFX_PERSONALINFO_H__FAD39CF4_50EC_42C2_BD08_83A2C5430D43__INCLUDED_)

In the source file of the property page's class, you will need to make a few changes as follows:

// PersonalInfo.cpp: implementation of the CPersonalInfo class.
//
//////////////////////////////////////////////////////////////////////
#include "StdAfx.h"
#include "PersonalInfo.h"

IMPLEMENT_DYNCREATE(CPersonalInfo, CPropertyPage)
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CPersonalInfo::CPersonalInfo(CWnd *pParent /*= NULL*/)
	: CPropertyPage(CPersonalInfo::IDD)
{

}

CPersonalInfo::~CPersonalInfo()
{

}

void CPersonalInfo::DoDataExchange(CDataExchange *pDX)
{
	CPropertyPage::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CPersonalInfo, CPropertyPage)

END_MESSAGE_MAP()

BOOL CPersonalInfo::OnInitDialog()
{
	CPropertyPage::OnInitDialog();

	return TRUE;
}

As done for the first property page, you must create and configure a class for each property page used in your application. Here is the header file for the second property page:

// Education.h: interface for the CEducation class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_EDUCATION_H__74710BD3_BEE1_4694_A4BF_163B24F92AEB__INCLUDED_)
#define AFX_EDUCATION_H__74710BD3_BEE1_4694_A4BF_163B24F92AEB__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include "resource.h"

class CEducation : public CPropertyPage  
{
	DECLARE_DYNCREATE(CEducation)

public:
	CEducation(CWnd *pParent = NULL);
	virtual ~CEducation();

	enum { IDD = IDD_EDUCATION };

protected:
	virtual void DoDataExchange(CDataExchange* pDX);
	virtual BOOL OnInitDialog();

	DECLARE_MESSAGE_MAP()
};

#endif // !defined(AFX_EDUCATION_H__74710BD3_BEE1_4694_A4BF_163B24F92AEB__INCLUDED_)

Here is the source file for the second property page:

// Education.cpp: implementation of the CEducation class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "Education.h"

IMPLEMENT_DYNCREATE(CEducation, CPropertyPage)
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CEducation::CEducation(CWnd *pParent /*= NULL*/)
	: CPropertyPage(CEducation::IDD)
{

}

CEducation::~CEducation()
{

}

void CEducation::DoDataExchange(CDataExchange *pDX)
{
	CPropertyPage::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CEducation, CPropertyPage)

END_MESSAGE_MAP()

BOOL CEducation::OnInitDialog()
{
	CPropertyPage::OnInitDialog();

	return TRUE;
}

Here is the header file of the third property page:

// SecurityBackground.h: interface for the CSecurityBackground class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_SECURITYBACKGROUND_H__092CDB83_27A6_43DE_88BD_C68BBAFFBC04__INCLUDED_)
#define AFX_SECURITYBACKGROUND_H__092CDB83_27A6_43DE_88BD_C68BBAFFBC04__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include "resource.h"

class CSecurityBackground : public CPropertyPage  
{
	DECLARE_DYNCREATE(CSecurityBackground)

public:
	CSecurityBackground(CWnd *pParent = NULL);
	virtual ~CSecurityBackground();

	enum { IDD = IDD_SECURITYBACKGROUND };

protected:
	virtual void DoDataExchange(CDataExchange* pDX);
	virtual BOOL OnInitDialog();

	DECLARE_MESSAGE_MAP()

};

#endif // !defined(AFX_SECURITYBACKGROUND_H__092CDB83_27A6_43DE_88BD_C68BBAFFBC04__INCLUDED_)

Here is the source file for the third property page:

// SecurityBackground.cpp: implementation of the CSecurityBackground class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "SecurityBackground.h"

IMPLEMENT_DYNCREATE(CSecurityBackground, CPropertyPage)
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CSecurityBackground::CSecurityBackground(CWnd *pParent /*= NULL*/)
	: CPropertyPage(CSecurityBackground::IDD)
{

}

CSecurityBackground::~CSecurityBackground()
{

}

void CSecurityBackground::DoDataExchange(CDataExchange *pDX)
{
	CPropertyPage::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CSecurityBackground, CPropertyPage)

END_MESSAGE_MAP()

BOOL CSecurityBackground::OnInitDialog()
{
	CPropertyPage::OnInitDialog();

	return TRUE;
}

As mentioned already, a property page is a child of a property sheet. This means that a property page cannot display by itself. Therefore, after creating the property page(s), you must create a property sheet. A property sheet is not a "physical" object. In other words it doesn't need or use a resource, only a class. Like a property page, a property sheet is treated like a dialog box but it is not one.

To create a class for a property sheet, you proceed the same way we did above. You can display the Add Class dialog box. If you are using MSVC .Net, you can select Generic C++ Class and click Open. On the Add Class or the Generic Add Class dialog box, you can set the class name as CCensusSheet for this exercise. In the Base Class edit box, you should type CPropertySheet.

There are two important aspects you must pay attention to when creating or configuring a class for a property sheet. One is required and the other is somewhat optional. The CPropertySheet class provides three constructors. The first is the default and doesn't take any argument. The other two constructors require a caption for the property sheet. The second takes as required argument an identifier for the caption, which you can create in the String Table. The third constructor would take a (null-terminated) string. For this example, we will use the constructor that accepts a string as argument). Therefore, the header file for the property sheet can be created as follows:

// CensusSheet.h: interface for the CCensusSheet class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_CENSUSSHEET_H__1311A0EE_306E_412B_9062_06F3315049C6__INCLUDED_)
#define AFX_CENSUSSHEET_H__1311A0EE_306E_412B_9062_06F3315049C6__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include "PersonalInfo.h"
#include "Education.h"
#include "SecurityBackground.h"

class CCensusSheet : public CPropertySheet  
{
	DECLARE_DYNAMIC(CCensusSheet)
public:
	CCensusSheet(LPCTSTR szCaption, CWnd *pParent = NULL, UINT iSelectPage = 0);
	virtual ~CCensusSheet();

protected:
	DECLARE_MESSAGE_MAP()
};

#endif // !defined(AFX_CENSUSSHEET_H__1311A0EE_306E_412B_9062_06F3315049C6__INCLUDED_)

The property sheet's source file can be set as follows:

// CensusSheet.cpp: implementation of the CCensusSheet class.
//
//////////////////////////////////////////////////////////////////////
IMPLEMENT_DYNAMIC(CCensusSheet, CPropertySheet)

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CCensusSheet::CCensusSheet(LPCTSTR szCaption, CWnd *pParent, UINT iSelectPage)
	: CPropertySheet(szCaption, pParent, iSelectPage)
{
}

CCensusSheet::~CCensusSheet()
{

}

BEGIN_MESSAGE_MAP(CCensusSheet, CPropertySheet)

END_MESSAGE_MAP()

The second issue you must pay attention to regarding the class used for your property sheet is the list of its property pages. The reason we stated that this aspect was optional is because you can deal with it either in the class of the property sheet or when you decide to use the property sheet. The issue is that you must provide a list of the property pages. If you want to create this list in the class of the property sheet, you must declare a variable for each property page in the header file of the property sheet. Here is an example:

// CensusSheet.h: interface for the CCensusSheet class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_CENSUSSHEET_H__1311A0EE_306E_412B_9062_06F3315049C6__INCLUDED_)
#define AFX_CENSUSSHEET_H__1311A0EE_306E_412B_9062_06F3315049C6__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include "PersonalInfo.h"
#include "Education.h"
#include "SecurityBackground.h"

class CCensusSheet : public CPropertySheet  
{
public:
	CCensusSheet(LPCTSTR szCaption, CWnd *pParent = NULL, UINT iSelectPage = 0);
	virtual ~CCensusSheet();

protected:
	DECLARE_MESSAGE_MAP()

private:
	CPersonalInfo	  PersInfo;
	CEducation	  Educ;
	CSecurityBackground SecBgrnd;
};

#endif // !defined(AFX_CENSUSSHEET_H__1311A0EE_306E_412B_9062_06F3315049C6__INCLUDED_)

To actually list the pages, you can call the CPropertySheet:AddPage() method and pass the property page's variable as argument. This can be done in the constructor of the property sheet:

CCensusSheet::CCensusSheet(LPCTSTR szCaption, CWnd *pParent, UINT iSelectPage)
	: CPropertySheet(szCaption, pParent, iSelectPage)
{
	AddPage(&PersInfo);
	AddPage(&Educ);
	AddPage(&SecBgrnd);
}

This makes the property sheet and its property page(s) ready for to use. At this time, you must have or create an application that will manage the showing or closing of the property sheet. This is done by deriving a class from CWinApp and overriding its InitInstance() method. Once again, you can use the Add Class or Generic C++ Class Wizard to create a the class. You can specify the Class Name as CCensusApp for this application and make sure you specify the Base Class as CWinApp. The header file would appear as follows:

// CensusApp.h: interface for the CCensusApp class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_CENSUSAPP_H__5B7793E3_B4C1_4709_A0D9_2A8A433C21BB__INCLUDED_)
#define AFX_CENSUSAPP_H__5B7793E3_B4C1_4709_A0D9_2A8A433C21BB__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class CCensusApp : public CWinApp  
{
public:
	CCensusApp();
	virtual ~CCensusApp();

// Overrides
public:
	virtual BOOL InitInstance();

// Implementation

	DECLARE_MESSAGE_MAP()
};

#endif // !defined(AFX_CENSUSAPP_H__5B7793E3_B4C1_4709_A0D9_2A8A433C21BB__INCLUDED_)

When overriding the InitInstance() method in the source file of the application, you will essentially declare a variable for the property sheet using the constructor you chose for the class. If you had already the list of property pages in the property sheet's class, your job can consist of just a few lines. Here is an example:

// CensusApp.cpp: implementation of the CCensusApp class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "CensusApp.h"
#include "CensusSheet.h"

BEGIN_MESSAGE_MAP(CCensusApp, CWinApp)

END_MESSAGE_MAP()
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CCensusApp::CCensusApp()
{

}

CCensusApp::~CCensusApp()
{

}

CCensusApp theApp;

BOOL CCensusApp::InitInstance()
{
	CCensusSheet NationalCensus("National Census");

	m_pMainWnd = &NationalCensus;
	NationalCensus.DoModal();

	return FALSE;
}

If you didn't create the list of property page(s) in the property sheet's class, you can create it when invoking the property sheet and add the property page(s) as needed. The source file of the application would look like this:

#include "censusapp.h"
#include "CensusSheet.h"
#include "PersonalInfo.h"
#include "Education.h"
#include "SecurityBackground.h"

BEGIN_MESSAGE_MAP(CCensusApp, CWinApp)

END_MESSAGE_MAP()

CCensusApp::CCensusApp(void)
{
}

CCensusApp::~CCensusApp(void)
{
}

CCensusApp theApp;

BOOL CCensusApp::InitInstance()
{
	CCensusSheet NationalCensus("National Census");

	CPersonalInfo	    PersInfo;
	CEducation                  Educ;
	CSecurityBackground SecBgrnd;

	NationalCensus.AddPage(&PersInfo);
	NationalCensus.AddPage(&Educ);
	NationalCensus.AddPage(&SecBgrnd);

	m_pMainWnd = &NationalCensus;
	NationalCensus.DoModal();

	return FALSE;
}

Both technique produce the same result:

 

Copyright © 1998-2007 FunctionX, Inc.