Enabling the Apply button in a Property Sheet

Override OnApply

In one of the dialog classes, override the OnApply button. It is not necessary to do this in all of the dialog classes, since clicking the Apply button calls the OnApply method for each of the dialog classes. OnApply has a default implementation in the CProperyPage class. If each page has a different action to be performed when Apply is clicked, then it is possible to override OnApply in more than one class. In this example, when the OnApply button is pressed, then a message is sent to the CView. Only one message needs to be sent, so only one OnApply needs to be overriden.


Add Event Handlers to determine when the Property Sheet has been modified

It is the programmer's responsibility to define when the Property Sheet changes. Usually, this will happen in response to a user's action. For instance, if a radio button changes or the value in an edit box changes, then the information in the Property Sheet has changed. To set the modified flag when such actions occur, map event handlers for these actions. In the event handler, call the SetModified method for the property page.

Here is an example of a property page named CPage1. When the user changes the edit box named Edit1, the modifed flag is set.

void CPage1::OnEnChangeEdit1()
{
	// TODO:  If this is a RICHEDIT control, the control will not
	// send this notification unless you override the CPropertyPage::OnInitDialog()
	// function and call CRichEditCtrl().SetEventMask()
	// with the ENM_CHANGE flag ORed into the mask.

	// TODO:  Add your control notification handler code here
	SetModified();
}


Send a message to the view

After following the above steps, the Apply button will now function, but the view is still not being changed. It is neessary to send  a message to the view from the OnApply button. When the view receives this message, it will update itself.

Define a message

Define a message in the header file for the page that overrode the OnApply method. There is a constant that defines the first number that can be used by the user for defining constants: WM_USER. By using this constant as a base, then it can be guaranteed that the user's constant definitions will not interfere with other constants already defined in the application.

//page1.h
#define WM_PROPCHANGE WM_USER + 6

Setting the pointer to the view

In order to send a message to the view, the property page needs to have a pointer to the view. In the Property Page class that has overriden the OnApply method, add a member variable of type CView*

CView* m_pView;

and a method that will set it.

void CPage1::SetView(CView* pView)
{
	m_pView = pView;
}

In the Property Sheet class constructor, call the SetView method of the Property Page, m_page1, that has overriden the OnApply button.

CPropSheet::CPropSheet(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)
	:CPropertySheet(pszCaption, pParentWnd, iSelectPage)
{
    AddPage(&m_page1);
    AddPage(&m_page2);
    m_page1.SetView((CView*) pParentWnd);
}

Change the CPropertySheet in the view from a local to a member pointer

When using the Apply button, the Property Sheet needs to be accessible from two different member functions. Therefore, it will be necessary to change the CPropertySheet variable in the OnFilePropertysheet method into a member variable. Because the constructor of the Property Sheet is being sent a pointer from the View, it will be necessary to create the Property Sheet as a pointer.
Note: The alternative is to create the Property Sheet as a variable, and to initialize it in the constructor's initialization list. The problem with this approach is that the pointer to this cannot be used in an initialization list without generating a warning.

CPropSheet* m_pPropSheet;
CPropsView::CPropsView()
{
    m_edit = "Default";
    m_check = FALSE;
    m_pPropSheet = new CPropSheet("Property Sheet", this, 0);
}

CPropsView::~CPropsView()
{
    delete m_pPropSheet;
}
void CPropsView::OnFilePropertysheet()
{
    m_pPropSheet->m_page1.m_edit = m_edit;
    m_pPropSheet->m_page2.m_checkbox = m_check;
    int result = m_pPropSheet->DoModal();
    if (result == IDOK)
    {
        m_edit = m_pPropSheet->m_page1.m_edit;
        m_check = m_pPropSheet->m_page2.m_checkbox;
        Invalidate();
    }
}

Send the message to the view

In the OnApply method, send the message to the view. There are two optional arguments to the PostMessage method. They are not needed in this example, but two values are being sent just as a demonstation.

BOOL CPage1::OnApply()
{
	// TODO: Add your specialized code here and/or call the base class
	m_pView->PostMessage(WM_PROPCHANGE, 1, 2);
	return CPropertyPage::OnApply();
}

Map the message in the view

The framework wizards cannot map user defined messages.

In the header for the view class, add the following method. All user defined message handlers have this format.

afx_msg LRESULT OnPropChange(WPARAM wParam, LPARAM lParam);

In the cpp class for the view, include the header for the Property Page that overrode the OnApply method. Also, add the message map for the user defined message. User defined messages use the ON_MESSAGE macro to map a message to a method.

#include "Page1.h"
BEGIN_MESSAGE_MAP(CPropsView, CView)
// ... other message maps are here ... // add this one to the list that is already there ON_MESSAGE(WM_PROPCHANGE, OnPropChange) END_MESSAGE_MAP()

In the implementation of the OnPropChange method, update the variables that control the view, and invalidate the client window. The wParam and lParam parameters are not needed, but they are sent to the output window just to demonstrate that additional information can be sent with the message.

LRESULT CPropsView::OnPropChange(WPARAM wParam, LPARAM lParam)
{
    TRACE("%d %d\n", wParam, lParam);
    m_edit = m_pPropSheet->m_page1.m_edit;
    m_check = m_pPropSheet->m_page2.m_checkbox;
    Invalidate();
    return 0L;
}

Example: Props