The Ex12a Example Revisited
Now we'll add a property sheet to Ex12a that allows the user to change the rich edit control's font characteristics. Of course, we could use the standard MFC CFontDialog function, but then you wouldn't learn how to create property sheets. Figure 12-5 shows the property sheet that you'll build as you continue with Ex12a.
If you haven't built Ex12a, follow the instructions that begin on page 285 to build it. If you already have Ex12a working with the Transfer menu commands, just continue on with these steps:
Use the resource editor to edit the application's main menu. In Resource View, edit the IDR_MAINFRAME menu resource to add a Format menu that looks like this.
The MFC library has defined the following command IDs for the new Format menu commands.
Caption
Command ID
&Default
ID_FORMAT_DEFAULT
&Selection
ID_FORMAT_SELECTION
Add appropriate prompt strings for the two menu commands using the Properties window.
Use Class View's Properties window to add the view class command and update command user interface message handlers. Select the CEx12aView class in Class View, and then add the following member functions.
Object ID
Event
Member Function
ID_FORMAT_DEFAULT
COMMAND
OnFormatDefault
ID_FORMAT_SELECTION
COMMAND
OnFormatSelection
ID_FORMAT_SELECTION
UPDATE_
COMMAND_UI
OnUpdateFormatSelection
Use the resource editor to add four property page dialog templates. Right-click on the RC file in Resource View and choose Add Resource from the shortcut menu. In the Add Resource dialog box, select the small property page template. The templates are shown here with their associated IDs:
Use the IDs listed below for the controls in the dialog boxes. Set the Auto Buddy and the Set Buddy Integer properties for the Spin control, and set the Group property for the IDC_FONT and IDC_COLOR radio buttons. Set the minimum value of IDC_FONTSIZE to 8 and its maximum value to 24.Use the MFC Class Wizard to create the classes CPage1, CPage2, CPage3, and CPage4. In each case, select CPropertyPage as the base class. Have the MFC Class Wizard generate the code for all these classes in the filesProperty.h and
Property.cpp by changing the filenames within the text boxes for the header file and the CPP file. When Visual Studio .NET asks you whether you want to merge the files, click Yes. Then add the data members shown here:
Dialog Box
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_FONT
SIZE
int
m_nFontSize
IDD_PAGE4
Spin control
IDC_SPIN1
Finally, use Class View's Properties window to override the OnInitDialog virtual function for CPage4.
Use the MFC Class Wizard to create a class derived from CPropertySheet. Select the name CFontSheet. Generate the code in the filesProperty.h and
Property.cpp , the same files you used for the property page classes. The following code shows these files with the added code in boldface:Property.h
#pragma once
// Property.h : header file
//
#define WM_USERAPPLY WM_USER + 5
extern CView* g_pView;
////////////////////////////////////////////////////////////////////
// CPage1 dialog
class CPage1 : public CPropertyPage
{
DECLARE_DYNCREATE(CPage1)
public:
CPage1();
virtual ~CPage1();
// Dialog Data
enum { IDD = IDD_PAGE1 };
int m_nFont;
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV
virtual BOOL OnApply();
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
};
////////////////////////////////////////////////////////////////////
// CPage2 dialog
class CPage2 : public CPropertyPage
{
DECLARE_DYNCREATE(CPage2)
public:
CPage2();
virtual ~CPage2();
// Dialog Data
enum { IDD = IDD_PAGE2 };
BOOL m_bBold;
BOOL m_bItalic;
BOOL m_bUnderline;
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
};
////////////////////////////////////////////////////////////////////
// CPage3 dialog
class CPage3 : public CPropertyPage
{
DECLARE_DYNCREATE(CPage3)
public:
CPage3();
virtual ~CPage3();
// Dialog Data
enum { IDD = IDD_PAGE3 };
int m_nColor;
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
};
////////////////////////////////////////////////////////////////////
// CPage4 dialog
class CPage4 : public CPropertyPage
{
DECLARE_DYNCREATE(CPage4)
public:
CPage4();
virtual ~CPage4();
// Dialog Data
enum { IDD = IDD_PAGE4 };
int m_nFontSize;
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV
// support
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
public:
virtual BOOL OnInitDialog();
};
////////////////////////////////////////////////////////////////////
// CFontSheet
class CFontSheet : public CPropertySheet
{
DECLARE_DYNAMIC(CFontSheet)
public:
CPage1 m_page1;
CPage2 m_page2;
CPage3 m_page3;
CPage4 m_page4;
public:
CFontSheet(UINT nIDCaption, CWnd* pParentWnd = NULL,
UINT iSelectPage = 0);
CFontSheet(LPCTSTR pszCaption, CWnd* pParentWnd = NULL,
UINT iSelectPage = 0);
virtual ~CFontSheet();
protected:
DECLARE_MESSAGE_MAP()
};
Property.cpp// Property.cpp : implementation file
#include "stdafx.h"
#include "Ex12a.h"
#include "Property.h"
CView* g_pView;
////////////////////////////////////////////////////////////////////
// CPage1 dialog
IMPLEMENT_DYNCREATE(CPage1, CPropertyPage)
CPage1::CPage1() : CPropertyPage(CPage1::IDD)
{
m_nFont = -1;
}
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);
DDX_Radio(pDX, IDC_FONT, m_nFont);
}
BEGIN_MESSAGE_MAP(CPage1, CPropertyPage)
END_MESSAGE_MAP()
////////////////////////////////////////////////////////////////////
// CPage1 message handlers
////////////////////////////////////////////////////////////////////
// CPage2 dialog
IMPLEMENT_DYNCREATE(CPage2, CPropertyPage)
CPage2::CPage2() : CPropertyPage(CPage2::IDD)
{
m_bBold = FALSE;
m_bItalic = FALSE;
m_bUnderline = FALSE;
}
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);
DDX_Check(pDX, IDC_BOLD, m_bBold);
DDX_Check(pDX, IDC_ITALIC, m_bItalic);
DDX_Check(pDX, IDC_UNDERLINE, m_bUnderline);
}
BEGIN_MESSAGE_MAP(CPage2, CPropertyPage)
END_MESSAGE_MAP()
////////////////////////////////////////////////////////////////////
// CPage2 message handlers
////////////////////////////////////////////////////////////////////
// CPage3 dialog
IMPLEMENT_DYNCREATE(CPage3, CPropertyPage)
CPage3::CPage3() : CPropertyPage(CPage3::IDD)
{
m_nColor = -1;
}
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);
DDX_Radio(pDX, IDC_COLOR, m_nColor);
}
BEGIN_MESSAGE_MAP(CPage3, CPropertyPage)
END_MESSAGE_MAP()
////////////////////////////////////////////////////////////////////
// CPage3 message handlers
////////////////////////////////////////////////////////////////////
// CPage4 dialog
IMPLEMENT_DYNCREATE(CPage4, CPropertyPage)
CPage4::CPage4() : CPropertyPage(CPage4::IDD)
{
m_nFontSize = 0;
}
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);
DDX_Text(pDX, IDC_FONTSIZE, m_nFontSize);
DDV_MinMaxInt(pDX, m_nFontSize, 8, 24);
}
BEGIN_MESSAGE_MAP(CPage4, CPropertyPage)
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)
END_MESSAGE_MAP()
////////////////////////////////////////////////////////////////////
// CFontSheet message handlers
Insert the following line in theEx12aView.h file:
#include "Property.h"
Add two data members and two prototypes to the CEx12aView class:private:
CFontSheet m_sh;
BOOL m_bDefault; // TRUE default format, FALSE selection
Now add the prototype for the private function Format:void Format(CHARFORMAT &cf);
Insert the prototype for the protected function OnUserApply before the DECLARE_MESSAGE_MAP macro:afx_msg LRESULT OnUserApply(WPARAM wParam, LPARAM lParam);
Edit and add code in the fileEx12aView.cpp .Map the user-defined WM_USERAPPLY message, as shown here:
ON_MESSAGE(WM_USERAPPLY, OnUserApply)
Add the following lines to the OnCreate function, just before the return 0 statement:CHARFORMAT cf;
Format(cf);
m_rich.SetDefaultCharFormat(cf);
Edit the view constructor to set default values for the property sheet data members, as follows:CEx12aView::CEx12aView() : m_sh(")
{
m_sh.m_page1.m_nFont = 0;
m_sh.m_page2.m_bBold = FALSE;
m_sh.m_page2.m_bItalic = FALSE;
m_sh.m_page2.m_bUnderline = FALSE;
m_sh.m_page3.m_nColor = 0;
m_sh.m_page4.m_nFontSize = 12;
g_pView = this;
m_bDefault = TRUE;
}
Edit the format command handlers, as shown here:void CEx12aView::OnFormatDefault()
{
m_sh.SetTitle("Default Format");
m_bDefault = TRUE;
m_sh.DoModal();
}
void CEx12aView::OnFormatSelection()
{
m_sh.SetTitle("Selection Format");
m_bDefault = FALSE;
m_sh.DoModal();
}
void CEx12aView::OnUpdateFormatSelection(CCmdUI* pCmdUI)
{
long nStart, nEnd;
m_rich.GetSel(nStart, nEnd);
pCmdUI->Enable(nStart != nEnd);
}
Add the following handler for the user-defined WM_USERAPPLY message:LRESULT CEx12aView::OnUserApply(WPARAM wParam, LPARAM lParam)
{
TRACE("CEx12aView::OnUserApply -- wParam = %x\n", wParam);
CHARFORMAT cf;
Format(cf);
if (m_bDefault) {
m_rich.SetDefaultCharFormat(cf);
}
else {
m_rich.SetSelectionCharFormat(cf);
}
return 0;
}
Add the Format helper function, as shown below, to set a CHARFORMAT structure based on the values of the property sheet data members:void CEx12aView::Format(CHARFORMAT& cf)
{
cf.cbSize = sizeof(CHARFORMAT);
cf.dwMask = CFM_BOLD | CFM_COLOR | CFM_FACE |
CFM_ITALIC | CFM_SIZE | CFM_UNDERLINE;
cf.dwEffects = (m_sh.m_page2.m_bBold ? CFE_BOLD : 0) |
(m_sh.m_page2.m_bItalic ? CFE_ITALIC : 0) |
(m_sh.m_page2.m_bUnderline ? CFE_UNDERLINE : 0);
cf.yHeight = m_sh.m_page4.m_nFontSize * 20;
switch(m_sh.m_page3.m_nColor) {
case -1:
case 0:
cf.crTextColor = RGB(0, 0, 0);
break;
case 1:
cf.crTextColor = RGB(255, 0, 0);
break;
case 2:
cf.crTextColor = RGB(0, 255, 0);
break;
}
switch(m_sh.m_page1.m_nFont) {
case -1:
case 0:
strncpy(cf.szFaceName, "Times New Roman" ,LF_FACESIZE);
break;
case 1:
strncpy(cf.szFaceName, "Arial" ,LF_FACESIZE);
break;
case 2:
strncpy(cf.szFaceName, "Courier New" ,LF_FACESIZE);
break;
}
cf.bCharSet = 0;
cf.bPitchAndFamily = 0;
}
Build and test the enhanced Ex12a application. Type some text, and then choose Default from the Format menu. Observe the TRACE messages in the Debug window as you click on property sheet tabs and click the Apply button. Try highlighting some text and then formatting the selection.
Apply Button Processing
You might be curious about the way the property sheet classes process the Apply button. In all the page classes, the overridden OnCommand functions enable the Apply button whenever a control sends a message to the page. This works fine for pages 1 through 3 in Ex12a, but for page 4, OnCommand is called during the initial conversation between the Spin control and its buddy.The OnApply virtual override in the CPage1 class sends a user-defined message to the view. The function finds the view in an expedient way—by using a global variable set by the view class. A better approach would be to pass the view pointer to the sheet constructor and then to the page constructor.The view class calls the property sheet's DoModal function for both default formatting and selection formatting. It sets the m_bDefault flag to indicate the mode. We don't need to check the return from DoModal because the user-defined message is sent for both the OK button and the Apply button. If the user clicks Cancel, no message is sent.