Program examples compiled using Visual C++ 6.0 (MFC 6.0) compiler on Windows XP Pro machine with Service Pack 2. Topics and sub topics for this Tutorial are listed below:
|
|
Property Sheets
You've already seen property sheets in Visual C++ and in many other modern Windows-based programs. A property sheet is a nice UI element that allows you to cram lots of categorized information into a small dialog. The user selects pages by clicking on their tabs. Windows offers a tab control that you can insert in a dialog, but it's more likely that you'll want to put dialogs inside the tab control. The MFC library supports this, and the result is called a property sheet. The individual dialogs are called property pages.
Building a Property Sheet
Follow these general steps to build a property sheet using the Visual C++ tools:
Property Sheet Data Exchange
The framework puts three buttons on a property sheet (See Figure 1 for example). Be aware that the framework calls the Dialog Data Exchange (DDX) code for a property page each time the user switches to and from that page. As you would expect, the framework calls the DDX code for a page when the user clicks OK, thus updating that page's data members. From these statements, you can conclude that all data members for all pages are updated when the user clicks OK to exit the sheet. All this with no C++ programming on your part! With a normal modal dialog, if the user clicks the Cancel button, the changes are discarded and the dialog class data members remain unchanged. With a property sheet, however, the data members are updated if the user changes one page and then moves to another, even if the user exits by clicking the Cancel button. What does the Apply button do? Nothing at all if you don't write some code. It won't even be enabled. To enable it for a given page, you must set the page's modified flag by calling SetModified(TRUE) when you detect that the user has made changes on the page. |
If you've enabled the Apply button, you can write a handler function for it in your page class by overriding the virtual CPropertyPage::OnApply function. Don't try to understand property page message processing in the context of normal modal dialogs; it's quite different. The framework gets a WM_NOTIFY message for all button clicks. It calls the DDX code for the page if the OK or Apply button was clicked. It then calls the virtual OnApply() functions for all the pages, and it resets the modified flag, which disables the Apply button. Don't forget that the DDX code has already been called to update the data members in all pages, so you need to override OnApply() in only one page class. What you put in your OnApply() function is your business, but one option is to send a user-defined message to the object that created the property sheet. The message handler can get the property page data members and process them. Meanwhile, the property sheet stays on the screen.
The MYMFCPRO Project Example Revisited
Now we'll add a property sheet to MYMFCPRO that allows the user to change the rich edit control's font characteristics. Of course, we could have used the standard MFC CFontDialog() function, but then you wouldn't have learned how to create property sheets. Figure 1 shows the property sheet that you'll build as you continue with MYMFCPRO.

Figure 1: The property sheet from MYMFCPRO.
If you haven't built MYMFCPRO, follow the instructions that begin under the MYMFCPRO example to build it. If you already have MYMFCPRO working with the Transfer menu commands, just continue on with the following long steps:
Use the resource editor to edit the application's main menu. Click on the ResourceView tab in the Workspace window. Edit the IDR_MAINFRAME menu resource to add a Format menu that looks something like this.

Figure 2: Adding new menus and its items.
Use the following command IDs for the new Format menu items.
|
Caption |
Command ID |
|
&Default |
ID_FORMAT_DEFAULT |
|
&Selection |
ID_FORMAT_SELECTION |
|
Table 1 |
|
Add appropriate prompt strings for the two menu items as needed.
Use ClassWizard to add the view class command and update command UI message handlers. Select the CMymfcproView class, and then add the following member functions.
|
Object ID |
Message |
Member Function |
|
ID_FORMAT_DEFAULT |
COMMAND |
OnFormatDefault() |
|
ID_FORMAT_SELECTION |
COMMAND |
OnFormatSelection() |
|
ID_FORMAT_SELECTION |
UPDATE_COMMAND_UI |
OnUpdateFormatSelection() |
|
Table 1 |
||

Figure 3: Using ClassWizard to add the view class commands and update command UI message handlers.
Use the resource editor to add four property page dialog templates. The templates are shown here with their associated IDs.

Figure 4: Using the resource editor to add Font property page dialog templates.

Figure 5: Using the resource editor to add Effects property page dialog templates.

Figure 6: Using the resource editor to add Color property page dialog templates.

Figure 7: Using the resource editor to add Size property page dialog templates.
Use the IDs in the table below for the controls in the dialogs. Set the Auto Buddy and the Set Buddy Integer properties for the spin button control, and set the Group property for the IDC_FONT and IDC_COLOR radio buttons. Use ClassWizard to create the classes CPage1, CPage2, CPage3, and CPage4. In each case, select:
CPropertyPage as the base class.
Click the Change button in ClassWizard's New Class dialog to generate the code for all these classes in the files Property.h and Property.cpp.
Then add the data members shown here.
|
Dialog ID |
Control |
Control ID |
Type |
Data Member |
|
IDD_PAGE1 |
First radio button |
IDC_FONT |
int |
m_nFont |
|
IDD_PAGE2 |
Bold check box |
IDC_BOLD |
BOOL |
m_bBold |
|
IDD_PAGE2 |
Italic check box |
IDC_ITALIC |
BOOL |
m_bItalic |
|
IDD_PAGE2 |
Underline check box |
IDC_UNDERLINE |
BOOL |
m_bUnderline |
|
IDD_PAGE3 |
First radio button |
IDC_COLOR |
int |
m_nColor |
|
IDD_PAGE4 |
Edit control |
IDC_FONTSIZE |
int |
m_nFontSize |
|
IDD_PAGE4 |
Spin button control |
IDC_SPIN1 |
- |
- |
|
Table 2 |
||||
|
Figure 8: New class creation dialog prompt. |

Figure 9: Creating the CPage1 class.

Figure 10: Changing the class header and implementation file names.

Figure 11: Using ClassWizard to create the new CPage2, CPage3, and CPage4 classes.

Figure 12: CPage2 new class information.
Continue for CPage3 and CPage4 classes…Then continue for the data member.

Figure 13: Adding data member variable.
Continue for other classes and for CPage4, set the minimum value of IDC_FONTSIZE to 8 and its maximum value to 24.

Figure 14: Setting the minimum value of IDC_FONTSIZE to 8 and its maximum value to 24.
Finally, use ClassWizard to add an OnInitDialog() message handler function for CPage4.

Figure 15: Adding an OnInitDialog() message handler function for CPage4.
Use ClassWizard to create a class derived from CPropertySheet. Choose the name CFontSheet. Generate the code in the files Property.h and Property.cpp, the same files you used for the property page classes. Listing 1 shows these files with the added code in orange color.

Figure 16: Using ClassWizard to create a CFontSheet class that derived from CPropertySheet.
|
PROPERTY.H #if !defined(AFX_PROPERTY_H__CD702F99_7495_11D0_8FDC_00C04FC2A0C2__INCLUDED_) #define AFX_PROPERTY_H_ _CD702F99_7495_11D0_8FDC_00C04FC2A0C2__INCLUDED_
#if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 // Property.h : header file //
#define WM_USERAPPLY WM_USER + 5 extern CView* g_pView;
//////////////////////////////////////////////////////////////////// // CPage1 dialog
class CPage1 : public CPropertyPage { DECLARE_DYNCREATE(CPage1)
// Construction public: CPage1(); ~CPage1();
// Dialog Data //{{AFX_DATA(CPage1) enum { IDD = IDD_PAGE1 }; int m_nFont; //}}AFX_DATA
// Overrides // ClassWizard generate virtual function overrides //{{AFX_VIRTUAL(CPage1) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV // support //}}AFX_VIRTUAL virtual BOOL OnApply(); virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); // Implementation protected: // Generated message map functions //{{AFX_MSG(CPage1) // NOTE: the ClassWizard will add member functions here //}}AFX_MSG DECLARE_MESSAGE_MAP() };
//////////////////////////////////////////////////////////////////// // CPage2 dialog
class CPage2 : public CPropertyPage { DECLARE_DYNCREATE(CPage2)
// Construction public: CPage2(); ~CPage2();
// Dialog Data //{{AFX_DATA(CPage2) enum { IDD = IDD_PAGE2 }; BOOL m_bBold; BOOL m_bItalic; BOOL m_bUnderline; //}}AFX_DATA
// Overrides // ClassWizard generate virtual function overrides //{{AFX_VIRTUAL(CPage2) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV // support //}}AFX_VIRTUAL virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); // Implementation protected: // Generated message map functions //{{AFX_MSG(CPage2) // NOTE: the ClassWizard will add member functions here //}}AFX_MSG DECLARE_MESSAGE_MAP()
};
//////////////////////////////////////////////////////////////////// // CPage3 dialog
class CPage3 : public CPropertyPage { DECLARE_DYNCREATE(CPage3)
// Construction public: CPage3(); ~CPage3();
// Dialog Data //{{AFX_DATA(CPage3) enum { IDD = IDD_PAGE3 }; int m_nColor; //}}AFX_DATA
// Overrides // ClassWizard generate virtual function overrides //{{AFX_VIRTUAL(CPage3) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV // support //}}AFX_VIRTUAL virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); // Implementation protected: // Generated message map functions //{{AFX_MSG(CPage3) // NOTE: the ClassWizard will add member functions here //}}AFX_MSG DECLARE_MESSAGE_MAP() };
//////////////////////////////////////////////////////////////////// // CPage4 dialog
class CPage4 : public CPropertyPage { DECLARE_DYNCREATE(CPage4)
// Construction public: CPage4(); ~CPage4();
// Dialog Data //{{AFX_DATA(CPage4) enum { IDD = IDD_PAGE4 }; int m_nFontSize; //}}AFX_DATA
// Overrides // ClassWizard generate virtual function overrides //{{AFX_VIRTUAL(CPage4) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV // support //}}AFX_VIRTUAL virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); // Implementation protected: // Generated message map functions //{{AFX_MSG(CPage4) virtual BOOL OnInitDialog(); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
//////////////////////////////////////////////////////////////////// // CFontSheet
class CFontSheet : public CPropertySheet { DECLARE_DYNAMIC(CFontSheet)
public: CPage1 m_page1; CPage2 m_page2; CPage3 m_page3; CPage4 m_page4;
// Construction public: CFontSheet(UINT nIDCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0); CFontSheet(LPCTSTR pszCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0);
// Attributes public:
// Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CFontSheet) //}}AFX_VIRTUAL
// Implementation public: virtual ~CFontSheet();
// Generated message map functions protected: //{{AFX_MSG(CFontSheet) // NOTE - the ClassWizard will add and remove member functions here. //}}AFX_MSG DECLARE_MESSAGE_MAP() };
//////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations // immediately before the previous line.
#endif // !defined(AFX_PROPERTY_H_ _CD702F99_7495_11D0_8FDC_00C04FC2A0C2__INCLUDED_)
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
PROPERTY.CPP // Property.cpp : implementation file //
#include "stdafx.h" #include "mymfcpro.h" #include "Property.h"
#ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[ ] = __FILE__; #endif
CView* g_pView; //////////////////////////////////////////////////////////////////// // CPage1 property page
IMPLEMENT_DYNCREATE(CPage1, CPropertyPage)
CPage1::CPage1() : CPropertyPage(CPage1::IDD) { //{{AFX_DATA_INIT(CPage1) m_nFont = -1; //}}AFX_DATA_INIT }
CPage1::~CPage1() { }
BOOL CPage1::OnApply() { TRACE("CPage1::OnApply\n"); g_pView->SendMessage(WM_USERAPPLY); return TRUE; }
BOOL CPage1::OnCommand(WPARAM wParam, LPARAM lParam) { SetModified(TRUE); return CPropertyPage::OnCommand(wParam, lParam); }
void CPage1::DoDataExchange(CDataExchange* pDX) { TRACE("Entering CPage1::DoDataExchange -- %d\n", pDX->m_bSaveAndValidate); CPropertyPage::DoDataExchange(pDX); //{{AFX_DATA_MAP(CPage1) DDX_Radio(pDX, IDC_FONT, m_nFont); //}}AFX_DATA_MAP }
BEGIN_MESSAGE_MAP(CPage1, CPropertyPage) //{{AFX_MSG_MAP(CPage1) // NOTE: the ClassWizard will add message map macros here //}}AFX_MSG_MAP END_MESSAGE_MAP()
//////////////////////////////////////////////////////////////////// // CPage1 message handlers
//////////////////////////////////////////////////////////////////// // CPage2 property page
IMPLEMENT_DYNCREATE(CPage2, CPropertyPage)
CPage2::CPage2() : CPropertyPage(CPage2::IDD) { //{{AFX_DATA_INIT(CPage2) m_bBold = FALSE; m_bItalic = FALSE; m_bUnderline = FALSE; //}}AFX_DATA_INIT }
CPage2::~CPage2() { }
BOOL CPage2::OnCommand(WPARAM wParam, LPARAM lParam) { SetModified(TRUE); return CPropertyPage::OnCommand(wParam, lParam); }
void CPage2::DoDataExchange(CDataExchange* pDX) { TRACE("Entering CPage2::DoDataExchange -- %d\n", pDX->m_bSaveAndValidate); CPropertyPage::DoDataExchange(pDX); //{{AFX_DATA_MAP(CPage2) DDX_Check(pDX, IDC_BOLD, m_bBold); DDX_Check(pDX, IDC_ITALIC, m_bItalic); DDX_Check(pDX, IDC_UNDERLINE, m_bUnderline); //}}AFX_DATA_MAP }
BEGIN_MESSAGE_MAP(CPage2, CPropertyPage) //{{AFX_MSG_MAP(CPage2) // NOTE: the ClassWizard will add message map macros here //}}AFX_MSG_MAP END_MESSAGE_MAP()
//////////////////////////////////////////////////////////////////// // CPage2 message handlers
//////////////////////////////////////////////////////////////////// // CPage3 property page
IMPLEMENT_DYNCREATE(CPage3, CPropertyPage)
CPage3::CPage3() : CPropertyPage(CPage3::IDD) { //{{AFX_DATA_INIT(CPage3) m_nColor = -1; //}}AFX_DATA_INIT }
CPage3::~CPage3() { }
BOOL CPage3::OnCommand(WPARAM wParam, LPARAM lParam) { SetModified(TRUE); return CPropertyPage::OnCommand(wParam, lParam); }
void CPage3::DoDataExchange(CDataExchange* pDX) { TRACE("Entering CPage3::DoDataExchange -- %d\n", pDX->m_bSaveAndValidate); CPropertyPage::DoDataExchange(pDX); //{{AFX_DATA_MAP(CPage3) DDX_Radio(pDX, IDC_COLOR, m_nColor); //}}AFX_DATA_MAP }
BEGIN_MESSAGE_MAP(CPage3, CPropertyPage) //{{AFX_MSG_MAP(CPage3) // NOTE: the ClassWizard will add message map macros here //}}AFX_MSG_MAP END_MESSAGE_MAP()
//////////////////////////////////////////////////////////////////// // CPage3 message handlers
//////////////////////////////////////////////////////////////////// // CPage4 property page
IMPLEMENT_DYNCREATE(CPage4, CPropertyPage)
CPage4::CPage4() : CPropertyPage(CPage4::IDD) { //{{AFX_DATA_INIT(CPage4) m_nFontSize = 0; //}}AFX_DATA_INIT }
CPage4::~CPage4() { }
BOOL CPage4::OnCommand(WPARAM wParam, LPARAM lParam) { SetModified(TRUE); return CPropertyPage::OnCommand(wParam, lParam); }
void CPage4::DoDataExchange(CDataExchange* pDX) { TRACE("Entering CPage4::DoDataExchange -- %d\n", pDX->m_bSaveAndValidate); CPropertyPage::DoDataExchange(pDX); //{{AFX_DATA_MAP(CPage4) DDX_Text(pDX, IDC_FONTSIZE, m_nFontSize); DDV_MinMaxInt(pDX, m_nFontSize, 8, 24); //}}AFX_DATA_MAP }
BEGIN_MESSAGE_MAP(CPage4, CPropertyPage) //{{AFX_MSG_MAP(CPage4) //}}AFX_MSG_MAP END_MESSAGE_MAP()
//////////////////////////////////////////////////////////////////// // CPage4 message handlers
BOOL CPage4::OnInitDialog() { CPropertyPage::OnInitDialog(); ((CSpinButtonCtrl*) GetDlgItem(IDC_SPIN1))->SetRange(8, 24); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } //////////////////////////////////////////////////////////////////// // CFontSheet
IMPLEMENT_DYNAMIC(CFontSheet, CPropertySheet)
CFontSheet::CFontSheet(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage) :CPropertySheet(nIDCaption, pParentWnd, iSelectPage) { }
CFontSheet::CFontSheet(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage) : CPropertySheet(pszCaption, pParentWnd, iSelectPage) { AddPage(&m_page1); AddPage(&m_page2); AddPage(&m_page3); AddPage(&m_page4); }
CFontSheet::~CFontSheet() { }
BEGIN_MESSAGE_MAP(CFontSheet, CPropertySheet) //{{AFX_MSG_MAP(CFontSheet) // NOTE - the ClassWizard will add and remove mapping macros here. //}}AFX_MSG_MAP END_MESSAGE_MAP()
//////////////////////////////////////////////////////////////////// // CFontSheet message handlers
|
Listing 1: The MYMFCPRO header and implementation file listings for the property page and property sheet classes.
Continue on next module...part 2.
Further reading and digging:
MSDN MFC 9.0 class library online documentation - latest version.
DCOM at MSDN.
COM+ at MSDN.
COM at MSDN.
Unicode and Multi-byte character set: Story and program examples.