Home

Property Sheets and Property Pages

 

Introduction to Property Sheets and Property Pages

 

Description

A property sheet is a control made of or more tabs called property page. A property sheet can have just one tab, that is, just one property page. Here is an example:

Digital Signature

In most other cases, a property page appears in a group with other pages. It functions like a group of pieces of paper placed on top of each other. Each piece is represented by a tab that allows the user to identify it:

Example of property pages on a property sheet

To use a property page, the user clicks the header, also called tab, of the desired page. This brings that page up and sends the other(s) in the background:

Property page selection

If the user needs access to another page, he or she can click the desired tab, which would bring that page in front and send the previously selected page to the back.

The pages are grouped and hosted by an object called a property sheet. The property pages of a property sheet are simply used to carry or hold other controls. The major idea of using a property sheet is its ability to have many controls available in a relatively smaller area.

There are two types of property sheets: modal and modeless. A modeless property sheet does not display buttons. A modal property sheet is equipped with command buttons that allow the user to make a decision after selecting items on the pages or changing the values of the page's controls. By default, after creating a property sheet, it is equipped with the OK, the Cancel, and the Apply buttons.

Practical LearningPractical Learning: Introducing Property Sheets

  1. Start Microsoft Visual Studio
  2. To create a new application, on the main menu, click File -> New Project...
  3. In the left list, click Visual C++. In the right list, click MFC Application
  4. Change the Name to Algebra1
  5. Click OK
  6. In the first page of the MFC Application Wizard, read the text and click Next
  7. In the second page of the wizard, click Dialog Based
  8. Click Next
  9. In the third page, click About Box to remove its check mark and click Next
  10. In the fourth page of the wizard, click Next
  11. In the fifth page of the wizard and in the General Classes list, click CAlgebra1Dlg
  12. In the Class Name, change the name of the class to CQuadrilateralDlg
  13. Change the .h File to QuadrilateralDlg.h
  14. Change the .cpp File to QuadrilateralDlg.cpp
     
    MFC Application Wizard
  15. Click Finish
  16. On the dialog box, click the TODO line and press Delete
  17. While the OK button is selected, press Delete
  18. Also delete the Cancel button
  19. If the Resource View is not visible, on the main menu, click View -> Other Windows -> Resource View.
    In the Resource View, right-click Algebra1.rc and click Add Resource...
  20. In the Add Resource dialog box, click Bitmap
     
    Add Resource
  21. Click Import...
  22. From the resources that accompany these lessons, select quadrilateral.bmp
  23. Click Open
  24. If the picture displays, close it.
    In the Resource View, expand the Bitmap node and click IDB object
  25. In the Properties window, click ID, type IDB_QUADRILATERAL and press Enter
  26. In the same way, import the circular.bmp and change its ID to IDB_CIRCULAR
  27. Import the dimensional.bmp and change its ID to IDB_DIMENSIONAL3
  28. In the Toolbox, click the Picture Control button Picture and click the dialog box
  29. In the Properties window, click Type and select Bitmap
  30. Click Image. Then click the arrow of its combo box and select IDB_QUADRILATERAL and heighten the dialog box so the bitmap would display completely
     
    Quadrilateral

Creating a Property Page

As mentioned already, a property is made of property pages. There are many steps you must follow to create a property sheet and its property pages.

A property page is an object or type CPropertyPage, which is a class derived from CDialog:

class CPropertyPage : public CDialog

In reality, you should start by creating a dialog box for each property page. Set the size as you wish. If you are creating many property pages, their dialog boxes don't have to have the same size (but it is better if they do). If they have different sizes, when the property sheet receives them, it will apply the largest width of one of the dialog boxes and the highest height of the property pages to apply it to the other property pages.

Besides the size of the dialog box, each must have the following properties (set in the Properties window):

  • Border: Thin
  • Disabled: True
  • Style: Child
  • Title Bar: True

Besides these, set the caption to the string you want to display on the tab.

Instead of creating te dialog box of a property page from scratch, Microsoft Visual C++ provides already-formatted objects ready to be designed. To use one of them, display the Add Resource dialog box and expand the Dialog node. Under Dialog, select either IDD_PROPPAGE_LARGE, IDD_PROPPAGE_MEDIUM, or IDD_PROPPAGE_SMALL:

Add Resource

As their names indicate, the sizes of the dialog boxes depend on the option. After selecting the desired option, click New. This would create a dialog box with the necessary options already set. You can then design the dialog box as you see fit.

After designing the dialog box, create its associated dialog box. When doing this, set the base class to CPropertyPage.

Practical LearningPractical Learning: Creating Property Pages

  1. Click the body of the dialog boxe to make sure it has focus.
    In the Properties window, change the following characteristics:
    Border: Thin
    Caption: Quadrilateral
    Disabled: True
    ID: IDD_QUADRILATERAL_DLG
    Style: Child
  2. In the Solution Explorer, expand the project and the Header Files node. Double-click QuadrilateralDlg.h
  3. Change the document as follows:
    // QuadrilateralDlg.h : header file
    //
    
    #pragma once
    
    // CQuadrilateralDlg dialog
    class CQuadrilateralDlg : public CPropertyPage
    {
    // Construction
    public:
    	CQuadrilateralDlg(CWnd* pParent = NULL); // standard constructor
    
    // Dialog Data
    	enum { IDD = IDD_QUADRILATERAL_DLG };
    
    	protected:
    	virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
    
    // Implementation
    protected:
    	HICON m_hIcon;
    
    	// Generated message map functions
    	virtual BOOL OnInitDialog();
    	afx_msg void OnPaint();
    	afx_msg HCURSOR OnQueryDragIcon();
    	DECLARE_MESSAGE_MAP()
    };
  4. Access the QuadrilateralDlg.cpp file and change it as follows:
    // QuadrilateralDlg.cpp : implementation file
    //
    
    #include "stdafx.h"
    #include "Algebra1.h"
    #include "QuadrilateralDlg.h"
    #include "afxdialogex.h"
    
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #endif
    
    // CQuadrilateralDlg dialog
    
    CQuadrilateralDlg::CQuadrilateralDlg(CWnd* pParent /*=NULL*/)
    	: CPropertyPage(CQuadrilateralDlg::IDD)
    {
    	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    }
    
    void CQuadrilateralDlg::DoDataExchange(CDataExchange* pDX)
    {
    	CPropertyPage::DoDataExchange(pDX);
    }
    
    BEGIN_MESSAGE_MAP(CQuadrilateralDlg, CPropertyPage)
    	ON_WM_PAINT()
    	ON_WM_QUERYDRAGICON()
    END_MESSAGE_MAP()
    
    // CQuadrilateralDlg message handlers
    
    BOOL CQuadrilateralDlg::OnInitDialog()
    {
    	CPropertyPage::OnInitDialog();
    
    	SetIcon(m_hIcon, TRUE);		// Set big icon
    	SetIcon(m_hIcon, FALSE);	// Set small icon
    
    	// TODO: Add extra initialization here
    
    	return TRUE;  // return TRUE  unless you set the focus to a control
    }
    
    void CQuadrilateralDlg::OnPaint()
    {
        if (IsIconic())
        {
    	CPaintDC dc(this); // device context for painting
    
    	SendMessage(WM_ICONERASEBKGND,
    		    reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
    
    	// Center icon in client rectangle
    	int cxIcon = GetSystemMetrics(SM_CXICON);
    	int cyIcon = GetSystemMetrics(SM_CYICON);
    	CRect rect;
    	GetClientRect(&rect);
    	int x = (rect.Width() - cxIcon + 1) / 2;
    	int y = (rect.Height() - cyIcon + 1) / 2;
    
    	// Draw the icon
    	dc.DrawIcon(x, y, m_hIcon);
        }
        else
        {
    	CPropertyPage::OnPaint();
        }
    }
    
    HCURSOR CQuadrilateralDlg::OnQueryDragIcon()
    {
        return static_cast<HCURSOR>(m_hIcon);
    }
  5. In the Resource View, expand the name of the project. Expand Algebra1.rc. Expand Dialog.
    Right-click Dialog and click Insert Dialog
  6. On the new dialog box, click OK and press Delete
  7. As the Cancel button is selected, press Delete
  8. In the Properties window, change the following characteristics of the dialog box:
    Border: Thin
    Caption: Circular Shapes
    Disabled: True
    ID: IDD_CIRCULAR_DLG
    Style: Child
  9. Add a picture control and set its bitmap to IDB_QUADRILATERAL
     
    Circular Shapes
  10. In the Resource View, right-click the name of the project and click Add Resource...
  11. Click Bitmap and click Import...
  12. Select circular.bmp and click Open
  13. To create a class for the dialog box, right-click the body of the dialog box and click Add Class...
  14. Set the Class Name to CCircularDlg
  15. Set the Base Class to CPropertyPage
     
    MFC Add Class Wizard
  16. Click Finish
  17. To create a new property page, in the Resource View, right-click Dialog and click Add Resource...
  18. In the Add Resource dialog box, expand Dialog and click IDD_PROPPAGE_LARGE
  19. Click New
  20. In the Properties window, change the following characteristics:
    Caption: 3-Dimensional
    ID: IDD_DIMENSION3_DLG
  21. Add a picture control and set its bitmap to IDB_QUADRILATERAL
     
    3-Dimensional
  22. Right-click the body of the dialog box and click Add Class...
  23. Set the Class Name to CDiamension3Dlg
  24. Set the Base Class to CPropertyPage
  25. Click Finish

Creating a Property Sheet

A property sheet is the host of property pages. A property sheet is based on the CPropertySheet class, which is derived from CWnd:

class CPropertySheet : public CWnd

To get a property sheet, create a class that is derived from CPropertySheet. To associate each property page to the property sheet, you must call the AddPage() member function of the CPropertySheet class. Its syntax is:

void AddPage(CPropertyPage *pPage);

You can call this member function where the property sheet is created. This could be in the constructor of the CPropertySheet-derived class.

Practical LearningPractical Learning: Creating a Property Sheet

  1. To create a new class, on the main menu, click Project -> Add Class...
  2. Click MFC Class and click Add
  3. Set the Class Name to CAlgebraSheet
  4. Set the Base Class to CPropertySheet
     
    MFC Add Class Wizard
  5. Click Finish
  6. Change the AlgebraSheet.h document as follows:
    #pragma once
    
    #include "QuadrilateralDlg.h"
    #include "CircularDlg.h"
    #include "Diamension3Dlg.h"
    
    // CAlgebraSheet
    
    class CAlgebraSheet : public CPropertySheet
    {
        DECLARE_DYNAMIC(CAlgebraSheet)
    
    public:
        CAlgebraSheet(UINT nIDCaption,
        		  CWnd* pParentWnd = NULL,
        		  UINT iSelectPage = 0);
        CAlgebraSheet(LPCTSTR pszCaption,
         		  CWnd* pParentWnd = NULL,
          		  UINT iSelectPage = 0);
        virtual ~CAlgebraSheet();
    
    private:
        CQuadrilateralDlg dlgQuadrilateral;
        CCircularDlg      dlgCircular;
        CDiamension3Dlg   dlgDiamension3;
    
    protected:
        DECLARE_MESSAGE_MAP()
    };
  7. Access the RepairOrderSheet.dpp file and change the constructor that takes a string as its first argument as follows:
    CAlgebraSheet::CAlgebraSheet(LPCTSTR pszCaption,
    			     CWnd* pParentWnd,
    			     UINT iSelectPage)
    	:CPropertySheet(pszCaption, pParentWnd, iSelectPage)
    {
    	this->AddPage(&dlgQuadrilateral);
    	this->AddPage(&dlgCircular);
    	this->AddPage(&dlgDiamension3);
    }

Displaying a Property Sheet

A property sheet is primarily a dialog box. As such, to display it, you can simply call the DoModal() member function of the CDialog class.

Practical LearningPractical Learning: Displaying a Property Sheet

  1. In the Solution Explorer, double-click Algebra1.cpp
  2. Change the file as follows:
    // Algebra1.cpp : Defines the class behaviors for the application.
    //
    
    #include "stdafx.h"
    #include "Algebra1.h"
    #include "QuadrilateralDlg.h"
    #include "AlgebraSheet.h"
    
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #endif
    
    
    . . . No Change
    
    BOOL CAlgebra1App::InitInstance()
    {
    	// InitCommonControlsEx() is required on Windows XP if an application
    	// manifest specifies use of ComCtl32.dll version 6 or later to enable
    	// visual styles.  Otherwise, any window creation will fail.
    	INITCOMMONCONTROLSEX InitCtrls;
    	InitCtrls.dwSize = sizeof(InitCtrls);
    	// Set this to include all the common control classes you want to use
    	// in your application.
    	InitCtrls.dwICC = ICC_WIN95_CLASSES;
    	InitCommonControlsEx(&InitCtrls);
    
    	CWinApp::InitInstance();
    
    	AfxEnableControlContainer();
    
    	// Create the shell manager, in case the dialog contains
    	// any shell tree view or shell list view controls.
    	CShellManager *pShellManager = new CShellManager;
    
    	// Standard initialization
    	// If you are not using these features and wish to reduce the size
    	// of your final executable, you should remove from the following
    	// the specific initialization routines you do not need
    	// Change the registry key under which our settings are stored
    	// TODO: You should modify this string to be something appropriate
    	// such as the name of your company or organization
    	SetRegistryKey(_T("Local AppWizard-Generated Applications"));
    
    	// CQuadrilateralDlg dlg;
    	CAlgebraSheet dlg(L"Elementary Algebra");
    
    	m_pMainWnd = &dlg;
    	
    	INT_PTR nResponse = dlg.DoModal();
    	if (nResponse == IDOK)
    	{
    		// TODO: Place code here to handle when the dialog is
    		//  dismissed with OK
    	}
    	else if (nResponse == IDCANCEL)
    	{
    		// TODO: Place code here to handle when the dialog is
    		//  dismissed with Cancel
    	}
    
    	// Delete the shell manager created above.
    	if (pShellManager != NULL)
    	{
    		delete pShellManager;
    	}
    
    	// Since the dialog has been closed, return FALSE so that we exit the
    	//  application, rather than start the application's message pump.
    	return FALSE;
    }
  3. To execute the application, press F5
     
    Elementary Algebra
  4. Close the dialog box and return to your programming environment
 
 
 

Characteristics of a Property Sheet and its Property Pages

 

Introduction

A property sheet is primarily a dialog box. As such it appears as a rectangular box with one or more tabs and buttons. In the Win32 library, a property page is created from a structure named PROPSHEETPAGE. To give you a connection to that structure, the CPropertyPage is equipped with a member variable named m_psh:

PROPSHEETPAGE m_psp;

In the Win32 library, a property sheet is created from a structure named PROPSHEETHEADER. This structure is represented in CPropertySheet class by a property named m_psh:

CPropertySheet::m_psh;

The m_psh gives access to the Win32 characteristics of a property sheet.

The Buttons of a Property Sheet

In its normal design, a property is usuqlly equipped with an OK and a cancel button. In some cases, it may have an Apply button. The functionality of the buttons of a property sheet are:

  • The OK button allows the user to validate any change(s) made on the control(s) of the property page(s) and close the dialog box. For example, if the user changes text from an edit box and clicks OK, the application that called the dialog will have the opportunity to acknowledge the change and act accordingly, and the property sheet would be closed
  • If the user clicks Cancel, the change(s) made on the property page's control(s) would be discarded, not taken into consideration, and the property sheet would be closed
  • If the property sheet has an Apply button, when the property sheet comes up, the Apply button on the property page is disabled. If the user changes something on the controls of the property page(s), the Apply button would become enabled. Once the Apply button is enabled, the user can use it. If the user clicks the Apply button, 1) any change(s) made on the control(s) is(are) sent to the object that called the property sheet, 2) the Apply button becomes disabled again, 3) the dialog box remains opened.

    This description is conforming to the standards or suggestions of the Microsoft Windows operating system.

In reality, you are completely free to do what you want with the buttons on the property sheet:

  • You can hide them
  • You can display them
  • You can completely delete (destroy) any unneeded button
  • You can add as many buttons as you judge necessary and as the bottom area can afford
  • You can change the captions of the buttons

Some of these issues we already know how to do. We already know that each control of an MFC application has an identifier. The buttons automatically added to a property sheet are identified as IDOK for the OK button and IDCANCEL for the Cancel button. If a property sheet has an Apply button, its identifier is ID_APPLY_NOW. If the property sheet has a Help button, the identifier of that button is IDHELP.

To manipulate any of these buttons, you can call the CWnd::GetDlgItem() member function to get a handle to the desired button and do what you want with it. Here is an example code you can use to change the caption of a button, hide another button, or simply destroy another:

BOOL CGeomeSheet::OnInitDialog()
{
    BOOL bResult = CPropertySheet::OnInitDialog();

    // TODO: Add your specialized code here
    // Change the caption of the OK button
    CButton *btnOK;

    btnOK = reinterpret_cast<CButton *>(GetDlgItem(IDOK));
    btnOK->SetWindowText("Sumbit");

    // Hide the Apply button
    CButton *btnApply;

    btnApply = reinterpret_cast<CButton *>(GetDlgItem(ID_APPLY_NOW));
    btnApply->ShowWindow(FALSE);

    // Destroy the Help button
    CButton *btnHelp;

    btnHelp = reinterpret_cast<CButton *>(GetDlgItem(IDHELP));
    btnHelp->DestroyWindow();

    return bResult;
}

To add a button, declare a pointer to CButton and call its Create() member function to initialize. We have seen various examples of how to dynamically create a control. If you decide to dynamically create a button, some of the issues you would have to deal with here are the location and probably the size of the new button, which have little to do with programming but with geometry. Here is an example:

BOOL CGeomeSheet::OnInitDialog()
{
    BOOL bResult = CPropertySheet::OnInitDialog();

    // TODO: Add your specialized code here
    // A pointer to button we will need
    CButton *btnApply;
    // We will need to location and dimensions of the Apply button
    CRect RectApply;

    // Get a handle to the Apply button
    btnApply = reinterpret_cast<CButton *>(GetDlgItem(ID_APPLY_NOW));
    // Get the location and the dimensions of the Apply button
    btnApply->GetWindowRect(&RectApply);

    // Convert the location and dimensions to screen coordinates
    ScreenToClient(&RectApply);

    CButton *Whatever = new CButton;

    Whatever->Create("&Whatever", WS_CHILD | WS_VISIBLE,
		    CRect(6, RectApply.top, 85,
    		    RectApply.top+RectApply.Height()),
		    this, 0x188);

    return bResult;
}

Another issue you would deal with is each of the messages sent by your dynamic button. Manipulating one button has no influence on the other(s). For example, if you destroy the Cancel button, the OK button does not move to the right. You would have to reposition any button as you judge it necessary.

We have already mentioned that, by standard and by design, the Apply button is disabled when the property sheet comes up. It is supposed to become enabled once the user gets any control "dirty"; that is, once a control, any control, is not the same as it was when the dialog box came up, the Apply button becomes available. To enable this control programmatically, once a control becomes dirty, call the CPropertyPage::SetModified(). Its syntax is:

void SetModified(BOOL bChanged = TRUE);

This member function is called by the control whose value you want to validate once the user has modified it. When the user clicks the OK button, the CPropertyPage::OnOK() event fires. By design, the changes made on the controls are acknowledged. The controls receive the status of "clean". The property sheet closes.

When the user clicks the Cancel button, the CPropertyPage::OnCancel() event fires. By design, the changes made on the controls are dismissed. The controls values are kept as they were when the property sheet displayed as long as the user did not previously click Apply since the property sheet was opened. The property sheet closes.

When the user clicks the Apply button, the CPropertyPage::OnApply() event fires. The changes that were made on the controls are acknowledged. The property sheet stays opened.

Once again, these behaviors are the default suggested by the standard but you can change them as you wish, although you should remain with these suggestions because your users may be more familiar with them.

Practical LearningPractical Learning: Configuring the Buttons of a Property Sheet

  1. On the main menu, click Project -> Class Wizard...
  2. In the Class Name combo box, select CAlgebraSheet
  3. Click the Virtual Funtions tab
  4. In the Virtual Functions list, double-click OnInitDialog
     
    MFC Class Wizard
  5. Click Edit Code
  6. To delete the Apply and the Help buttons, change the file as follows:
    BOOL CAlgebraSheet::OnInitDialog()
    {
    	BOOL bResult = CPropertySheet::OnInitDialog();
    
    	// TODO:  Add your specialized code here
    	// We will need a pointer to each button
    	CButton *btnOK, *btnCancel, *btnApply, *btnHelp;
    	// We will need to location and dimensions of Apply and Help
    	CRect RectApply, RectHelp;
    	
    	// Get handles to the OK and Cancel buttons
    	btnOK = reinterpret_cast<CButton *>(GetDlgItem(IDOK));
    	btnCancel = reinterpret_cast<CButton *>(GetDlgItem(IDCANCEL));
    	
    	// Get a handle to the Apply button
    	btnApply = reinterpret_cast<CButton *>(GetDlgItem(ID_APPLY_NOW));
    	// Get the location and the dimensions of the Apply button
    	btnApply->GetWindowRect(&RectApply);
    	
    	// Get a handle to the Help button
    	btnHelp = reinterpret_cast<CButton *>(GetDlgItem(IDHELP));
    	// Get the location and the dimensions of the Help button
    	btnHelp->GetWindowRect(&RectHelp);
    	
    	// Dismiss the Apply and the Help buttons
    	btnApply->DestroyWindow();
    	btnHelp->DestroyWindow();
    	
    	// Convert the location and dimensions to screen coordinates
    	ScreenToClient(&RectApply);
    	ScreenToClient(&RectHelp);
    	
    	// Put the OK button where the Apply button was
    	btnOK->SetWindowPos(NULL, RectApply.left, RectApply.top, 0, 0,
    			    SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW);
    
    	// Put the Cancel button where the Help button was
    	btnCancel->SetWindowPos(NULL, RectHelp.left, RectHelp.top, 0, 0,
    				SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW);
    
    	return bResult;
    }
  7. To test the application, press F9
     
    Elementary Algebra
  8. Close the application and return to your programming environment
 
 
   
 

Home Copyright © 2010 FunctionX, Inc.