Now we'll add a property sheet to EX13A 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 13-5 shows the property sheet that you'll build as you continue with EX13A.
Figure 13-5. The property sheet from EX13A.
If you haven't built EX13A, follow the instructions that begin under the EX13A Example to build it. If you already have EX13A working with the Transfer menu commands, just continue on with these steps:
Use the following command IDs for the new Format menu items.
Caption | Command ID |
&Default | ID_FORMAT_DEFAULT |
&Selection | ID_FORMAT_SELECTION |
Add appropriate prompt strings for the two menu items.
Object ID | Message | Member Function |
ID_FORMAT_DEFAULT | COMMAND | OnFormatDefault |
ID_FORMAT_SELECTION | COMMAND | OnFormatSelection |
ID_FORMAT_SELECTION | UPDATE_COMMAND_UI | OnUpdateFormatSelection |
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. Set the minimum value of IDC_FONTSIZE to 8 and its maximum value to 24.
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 | 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 |
Finally, use ClassWizard to add an OnInitDialog message handler function for CPage4.
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 "ex13a.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 |
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);
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:
CEx13aView::CEx13aView() : 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 CEx13aView::OnFormatDefault() { m_sh.SetTitle("Default Format"); m_bDefault = TRUE; m_sh.DoModal(); } void CEx13aView::OnFormatSelection() { m_sh.SetTitle("Selection Format"); m_bDefault = FALSE; m_sh.DoModal(); } void CEx13aView::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 CEx13aView::OnUserApply(WPARAM wParam, LPARAM lParam) { TRACE("CEx13aView::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 CEx13aView::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: strcpy(cf.szFaceName, "Times New Roman"); break; case 1: strcpy(cf.szFaceName, "Arial"); break; case 2: strcpy(cf.szFaceName, "Courier New"); break; } cf.bCharSet = 0; cf.bPitchAndFamily = 0; }
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 EX13A, but for page 4, OnCommand is called during the initial conversation between the spin button 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 wayby 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.