Click here to Skip to main content
15,893,508 members
Articles / Desktop Programming / MFC

Resource ID Organiser Add-In for Visual C++ 5.0/6.0/.NET

Rate me:
Please Sign up or sign in to vote.
4.98/5 (71 votes)
10 Jan 2005CPOL25 min read 533K   12.1K   201  
An application/add-in to organise and renumber resource symbol IDs
/************************************************************************
 *
 *                   Extended dialog classes
 *
 *    Written by Anna Metcalfe (code@annasplace.me.uk)
 *
 ************************************************************************
 *                                                                       
 *  Filename    : NGDialog.cpp
 *
 *  Compiler    : Microsoft Visual C++ 6.0, Service Pack 3 or later
 *                Microsoft Visual C++.NET
 *                                                                       
 *  Target                                                               
 *  Environment : Win98/Me/NT/2000/XP
 *
 *  NOTE:
 *
 *    Your are free to use this code in your own products, PROVIDED
 *    this notice is not removed or modified.
 *
 ************************************************************************/

#include "StdAfx.h"
#include <AfxPriv.h>			// For HID_BASE_CONTROL definition

#include "NGDebugTrace.h"
#include "NGMacros.h"
#include "NGUtils.h"
#include "NGDialog.h"


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


// ID value for ComboBox edit controls
// Refer to "C/C++ Q & A" in the April 1996 edition of MSJ for more details
#define	IDC_COMBOEDIT	1001


/////////////////////////////////////////////////////////////////////////////
// Helper functions (NOT exported)
//
// These should really be virtuals in CNGDialog and CNGPropertyPage
// The only reason they aren't is that this way we don't end up with
// two sets of identical code.
//
// One day I'll get around to building a C++ class template to overcome this...

typedef enum {	CTRL_NONE = -1,
				CTRL_STATIC,
				CTRL_BUTTON,
				CTRL_EDIT,
				CTRL_LISTBOX,
				CTRL_COMBOBOX,
				CTRL_LISTCTRL,
				CTRL_TREECTRL,
				CTRL_SLIDER,
				CTRL_UNKNOWN } CTRL_TYPE;


//	Use ::GetClassName() in the Win32 SDK to determine the type
//	of a control from its hWnd
static CTRL_TYPE IdentifyCtrl(HWND hWnd)
{	
	// These are the window class names for the most common ones!
	static LPCTSTR szWndClass[] = {
									_T("static"),				// Static text
									_T("button"),				// Button
									_T("edit"),					// Edit box
									_T("listbox"),				// List box
									_T("combobox"),				// Combo box
									_T("SysListView32"),		// List control
									_T("SysTreeView32"),		// Tree control
									_T("msctls_trackbar32")		// Slider control
								};
	int eType = CTRL_NONE;
	static TCHAR szClass[20];

	if (::IsWindow(hWnd))
	{
		// Get the window class of hWnd
		::GetClassName(hWnd, szClass, sizeof(szClass) );

		for (eType = 0; eType < CTRL_UNKNOWN; eType++)
		{
			if (::lstrcmpi( szWndClass[eType], szClass ) == 0)
			{
				break;
			}
		}
	}	
	return (CTRL_TYPE)eType;	
}



// Given the HWND of a control, return it's ID.
// This function is only necessary to handle radio buttons (nasty, horrible things!)
static UINT GetCtrlID(CWnd* pDlg, HWND hWndCtrl)
{
	UINT uCtrlID = 0;
	if (::IsChild(pDlg->GetSafeHwnd(), hWndCtrl))
	{
		while ( (hWndCtrl != NULL) && (::GetParent(hWndCtrl) != pDlg->GetSafeHwnd()) )
		{
			hWndCtrl = ::GetParent(hWndCtrl);
		}
		// Find out whether we're dealing with a radio button...
		if (::SendMessage(hWndCtrl, WM_GETDLGCODE, 0, 0L) & DLGC_RADIOBUTTON)
		{
			// ...Radio Buttons are actually multiple controls, with only the first
			// (usually) having an ID. Hence we need to walk up the Z order to
			// the first one, which should have the group property (WS_GROUP)
			// and a valid ID...
 			while ( (::GetWindowLong(hWndCtrl, GWL_STYLE) & WS_GROUP) == 0)
 			{
 				hWndCtrl = ::GetNextWindow(hWndCtrl, GW_HWNDPREV);
 			}
		}
		uCtrlID = ::GetDlgCtrlID(hWndCtrl);	// Found the group control for this radio button
	}
	return uCtrlID;
}


// Identifies control notifications which could result in a change in the
// VALUE of a control (not necessarily the same as its contents!)
// TO DO: Handle slider and other common controls correctly
static BOOL IsCtrlValueChanging(CWnd* pDlg, UINT uID, HWND hWnd, int nCode)
{
	//TRACE1("Control ID: %d\n", uID);
	BOOL bResult = FALSE;
	switch (::IdentifyCtrl(hWnd))
	{
		case CTRL_EDIT:
			//TRACE1("\tEdit Control: nCode = %d\n", nCode);
			if (EN_KILLFOCUS == nCode)
			{
				CEdit* pEdit = (CEdit*)pDlg->GetDlgItem(uID);
				if (pEdit->GetModify())			// Edit control modified since last UpdateData()
				{
					pEdit->SetModify(FALSE);	// Set as unmodified As UpdateData() about to be called
					bResult = TRUE;
				}
			}
			break;

		case CTRL_BUTTON:
			if (BN_CLICKED == nCode)
			{
				// TO DO: Walk to parent radio button if ID is IDC_STATIC
				// If we don't do this pBtn will be NULL and this will fail
				CButton* pBtn = (CButton*)pDlg->GetDlgItem(uID);
				if (pBtn != NULL)
				{
					UINT nStyle = pBtn->GetButtonStyle();
					if ( ( (nStyle & BS_CHECKBOX) == BS_CHECKBOX) ||
						 ( (nStyle & BS_AUTOCHECKBOX) == BS_AUTOCHECKBOX) )
					{
						//TRACE1("\tCheckbox Control: nCode = %d\n", nCode);
						bResult = TRUE;
					}
					else if ( ( (nStyle & BS_RADIOBUTTON) == BS_RADIOBUTTON) ||
						 ( (nStyle & BS_AUTORADIOBUTTON) == BS_AUTORADIOBUTTON) )
					{
						//TRACE1("\tRadio Button Control: nCode = %d\n", nCode);
						bResult = TRUE;
					}
					else
					{
						//TRACE1("\tOther button Control: nCode = %d\n", nCode);
						bResult = TRUE;
					}
				}
			}
			break;

		case CTRL_LISTBOX:
			//TRACE1("\tListBox Control: nCode = %d\n", nCode);
			if (LBN_SELCHANGE == nCode)
			{
				bResult = TRUE;
			}
			break;

		case CTRL_COMBOBOX:
			//TRACE1("\tComboBox Control: nCode = %d\n", nCode);
			if (CBN_SELCHANGE == nCode)
			{
				bResult = TRUE;
			}
			else if (CBN_KILLFOCUS == nCode)
			{
				// When a combo box loses the focus we need to treat the edit control
				// within it in the same way as any other edit control.
				// ComboBox edit controls have an ID of 1001 [refer to "C/C++ Q & A"
				// in the April 1996 edition of MSJ for more details]
				CComboBox* pComboBox = (CComboBox*)pDlg->GetDlgItem(uID);
				ASSERT(pComboBox != NULL);

				CEdit* pEdit = (CEdit*)pComboBox->GetDlgItem(IDC_COMBOEDIT);
				if ( (pEdit != NULL) && (pEdit->GetModify()) )
				{								// If the edit control has been modified since the
					pEdit->SetModify(FALSE);	// last UpdateData(), mark it as unmodified
					bResult = TRUE;				// [UpdateData() is about to be called]
				}
			}
			break;

		case CTRL_SLIDER:	// TO DO: Crack WM_NOTIFY messages
			//TRACE1("\tSlider Control: nCode = %d\n", nCode);
			break;

		default:
			break;
	}
	return bResult;
}


/////////////////////////////////////////////////////////////////////////////
// CNGDialog class

IMPLEMENT_DYNAMIC(CNGDialog, CNGDialog_BASE)

CNGDialog::CNGDialog(UINT uID /*= 0*/, CWnd* pParentWnd /*= NULL*/)
	: CNGDialog_BASE(uID, pParentWnd)
{
	m_pDoc				= NULL;
	m_pServer			= NULL;
	m_bModified			= false;
	m_bLockCtrlUpdates	= false;

	m_rectInitialPosition.SetRectEmpty();
	m_rectPosition.SetRectEmpty();

	//{{AFX_DATA_INIT(CNGDialog)
	//}}AFX_DATA_INIT
}


CNGDialog::~CNGDialog(void)
{
}


void CNGDialog::DoDataExchange(CDataExchange* pDX)
{
	CNGDialog_BASE::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CNGDialog)
	//}}AFX_DATA_MAP

	// These are out of Classwizard's grasp as it can't handle the syntax
}


BEGIN_MESSAGE_MAP(CNGDialog, CNGDialog_BASE)
	//{{AFX_MSG_MAP(CNGDialog)
	ON_WM_MOVE()
	ON_WM_HELPINFO()
	ON_WM_CONTEXTMENU()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()



//	Override the default message processing to try to figure out
//	when the value of a control has changed
//
//	NOTES:
//		1.	UpdateData(TRUE) is automatically called if the control value
//			could have changed, and DDV validation succeeds
//
//		2.	The detection of changed controls is not perfect, and hence the Apply
//			button could be erroneously enabled under certain circumstances.
//
BOOL CNGDialog::OnCommand(WPARAM wParam, LPARAM lParam) 
{
	BOOL bResult = FALSE;

	// Crack message parameters
	// This bit was nicked from CPropertySheet::OnCommand()
	UINT uID		= LOWORD(wParam);
	HWND hWnd		= (HWND)lParam;
	int nCode		= HIWORD(wParam);

	UINT uCtrlID	= uID;						// We'll need this later

	// If the command notification was a control value change
	// which could be linked up via DDX update the DDX member
	// vars and send a WM_COMMAND notification on the affected
	// control (this allows a generic ON_COMMAND handler to be
	// used, irrespective of the type of the control)

	if (hWnd != NULL)
	{
		// Catch ENTER keypresses on a control
		// and convert to a CN_COMMAND call for that control
		// This allows the control focused when ENTER was pressed to receive
		// the change notification...
		if (uID == IDOK)
		{
			// Walk up from the focused window until we find a valid control
			HWND hWndFocusCtrl = ::GetFocus();
			while ( (hWndFocusCtrl != NULL) && (::GetParent(hWndFocusCtrl) != m_hWnd) )
			{
				hWndFocusCtrl=::GetParent(hWndFocusCtrl);
			}
			
			// If the focused control is the one we orignially received the 
			// notification from it must be the OK button itself, so
			// let it through unchanged
			if ( (hWndFocusCtrl == NULL) || (hWndFocusCtrl == hWnd) )
			{
				return CNGDialog_BASE::OnCommand(wParam, lParam);
			}

			// Fire a CN_COMMAND notification on the focused control
			hWnd = hWndFocusCtrl;

			wParam = MAKEWPARAM(uID, CN_COMMAND);
			lParam = (LPARAM)hWnd;
			::SendMessage(hWnd, WM_COMMAND, wParam, lParam);

			// Move the focus to the next control
			::PostMessage(m_hWnd, WM_NEXTDLGCTL, 0, 0L);

			bResult = TRUE;			// Prevent dialog from closing
		}	
		else if (nCode == CN_COMMAND)
		{
			uCtrlID = ::GetCtrlID(this, hWnd);
			hWnd = ::GetDlgItem(GetSafeHwnd(), uCtrlID);
			ASSERT(uCtrlID != 0);
			ASSERT(uCtrlID != 65535);

			// If the ID of the control in the message isn't the same as that
			// of the control we've identified, pass it through
			// (this can happen with radio buttons)
			if (uID != uCtrlID)
			{
				bResult = CNGDialog_BASE::OnCommand(wParam, lParam);
			}
		}
		else
		{
			// All non-button controls end up here...
			bResult = CNGDialog_BASE::OnCommand(wParam, lParam);
		}
		if (::IsCtrlValueChanging(this, uCtrlID, hWnd, nCode) && !m_bLockCtrlUpdates)
		{
			bResult = OnCtrlValueChanging(uCtrlID, hWnd, nCode);

		}
	}
	else
	{
		bResult = CNGDialog_BASE::OnCommand(wParam, lParam);
	}
	return bResult;
}


// Called when the value of a control has been changed
BOOL CNGDialog::OnCtrlValueChanging(UINT uCtrlID, HWND hWnd, int nCode)
{
	UNREFERENCED_PARAMETER(hWnd);
	UNREFERENCED_PARAMETER(nCode);

	BOOL bResult = UpdateData(TRUE);			// Try to retrieve dialog data via DoDataExchange()
	if (bResult)
	{											// Validation succeeded...
		OnCmdMsg(uCtrlID, 0, NULL, NULL);		// ...so generate a WM_COMMAND notification...
		SetModified();							// ...and mark the dialog as changed
	}
	return bResult;
}


// This override is necessary to prevent re-entrant calls to UpdateData() via OnCommand()
void CNGDialog::OnOK(void)
{
	ASSERT_VALID(this);

	m_bLockCtrlUpdates	= true;

	CNGDialog_BASE::OnOK();

	m_bLockCtrlUpdates	= false;
}


/////////////////////////////////////////////////////////////////////////////
// CNGDialog Help Support

// Context help generated by pressing F1 or using the "?" button on the title
// bar of the dialog.
BOOL CNGDialog::OnHelpInfo(HELPINFO* pHelpInfo) 
{
	if (pHelpInfo->iContextType == HELPINFO_WINDOW)
	{
		// Launch WinHelp or HtmlHelp
		// The HELP_WM_HELP flag brings up pop-up help and expects an array 
		// of DWORD pairs of the control ID and the context help ID
		UINT uCtrlID = pHelpInfo->iCtrlId;
		if ( (uCtrlID != 0) && (uCtrlID != IDC_STATIC) )
		{
			CTRLID_HELPID_PAIR dwHelpIds(	uCtrlID,
											uCtrlID + HID_BASE_CONTROL);

			::WinHelp(	(HWND)pHelpInfo->hItemHandle,
						AfxGetApp()->m_pszHelpFilePath,	
						HELP_WM_HELP,
						(DWORD)&dwHelpIds);
		}
	}
	return TRUE;
}


//	Context help generated by right clicking the control and selecting the
//	"Whats this ?" menu.
void CNGDialog::OnContextMenu(CWnd* pWnd, CPoint point) 
{
 	// The title bar has a system provided context menu which is displayed for
	// a right mouse button up
	if (HTCLIENT == OnNcHitTest(point))
	{
		//TRACE("OnContextMenu - HTCIENT\n");
		CWnd* pChildWnd;
		if (pWnd == this) //if the control on the dialog is disabled
		{
			// get the hWnd of the disabled child window
			ScreenToClient(&point);
			pChildWnd = ChildWindowFromPoint(point, CWP_ALL);
		}
		else
		{
			// for OK, Cancel and Apply (if enabled) buttons
			pChildWnd = pWnd;
		}

		// Launch WinHelp or HtmlHelp
		// The HELP_CONTEXTMENU flag brings up the "Whats this ?" menu and selecting
		// the menu in turn brings up the pop-up help. It expects an array 
		// of DWORD pairs of the control ID and the context help ID
		
		UINT uCtrlID = pChildWnd->GetDlgCtrlID();
		if ( (uCtrlID != 0) && (uCtrlID != IDC_STATIC) )
		{
			CTRLID_HELPID_PAIR dwHelpIds(	uCtrlID,
											uCtrlID + HID_BASE_CONTROL);

			::WinHelp(	(HWND)pWnd->m_hWnd,
						AfxGetApp()->m_pszHelpFilePath,
						HELP_CONTEXTMENU,
						(DWORD)&dwHelpIds);
		}
	}
}

/////////////////////////////////////////////////////////////////////////////
// CNGDialog operations

BOOL CNGDialog::SetServer(CObject* pServer)
{
	m_pServer = pServer;
	ASSERT_VALID(m_pServer);

	return (m_pServer != NULL);
}


BOOL CNGDialog::SetDocument(CDocument* pDocument)
{
	m_pDoc = pDocument;
	ASSERT_VALID(m_pDoc);

	return (m_pDoc != NULL);
}


void CNGDialog::SetModified(BOOL bChanged /*= TRUE*/)
{
	m_bModified = bChanged;
}


BOOL CNGDialog::EnableDlgControl(UINT uID, BOOL bEnable)
{
	CWnd* pWnd = GetDlgItem(uID);
    if (NULL != pWnd)
	{
		pWnd->EnableWindow(bEnable);
		return TRUE;
	}
	ASSERT(FALSE);
	return FALSE;
}

	
BOOL CNGDialog::ShowDlgControl(UINT uID, BOOL bShow)
{
	CWnd* pWnd = GetDlgItem(uID);
    if (NULL != pWnd)
	{
		pWnd->ShowWindow(bShow);
		return TRUE;
	}
	ASSERT(FALSE);
	return FALSE;
}

	
BOOL CNGDialog::SetReadOnly(UINT uID, BOOL bReadOnly /*= TRUE*/)
{
	CEdit* pCtrl = static_cast<CEdit*>( GetDlgItem(uID) );
    if (NULL != pCtrl)
	{
		pCtrl->SetReadOnly(bReadOnly);
		return TRUE;
	}
	ASSERT(FALSE);
	return FALSE;
}


BOOL CNGDialog::UpdateData(BOOL bSaveAndValidate /*= TRUE*/)
{
	if (IsWindow(m_hWnd))
	{
		return CNGDialog_BASE::UpdateData(bSaveAndValidate);
	}
	return FALSE;
}


/////////////////////////////////////////////////////////////////////////////
// CNGPropertySheet overrides

// Override to initialise data BEFORE the screen controls
BOOL CNGDialog::OnInitDialog(void)
{
	// Update initial data from the server
	OnInitData();

	BOOL bResult = CNGDialog_BASE::OnInitDialog();
	
	SetInitialPosition();
	
	return bResult;
}


void CNGDialog::OnInitData(void)
{
	if (IsWindow(m_hWnd))
	{
		UpdateData(FALSE);
	}
}


/////////////////////////////////////////////////////////////////////////////
// CNGPropertySheet message handlers

void CNGDialog::OnMove(int x, int y) 
{
	CNGDialog_BASE::OnMove(x, y);
	
	GetWindowRect(&m_rectPosition);
}


/////////////////////////////////////////////////////////////////////////////
// Implementation

void CNGDialog::SetInitialPosition(void)
{
	// If we've got a position from last time use it to position
	// our window this time
	if (!m_rectInitialPosition.IsRectEmpty())
	{
		// As the property sheet is not scaleable at the moment we need
		// to make sure we change the size of m_rectPosition to match
		// the size of the window as it is now (remember that adding or
		// deleting pages between invokations may change the size)
		m_rectInitialPosition.BottomRight() =
							CPoint(	m_rectInitialPosition.left + m_rectPosition.Width(),
									m_rectInitialPosition.top + m_rectPosition.Height() );


		MoveWindow(m_rectInitialPosition);
	}
}


/////////////////////////////////////////////////////////////////////////////
// CNGDialog message handlers



/////////////////////////////////////////////////////////////////////////////
// CNGPropertyPage property page

IMPLEMENT_DYNAMIC(CNGPropertyPage, CNGPropertyPage_BASE)


CNGPropertyPage::CNGPropertyPage(	UINT uID /*= 0*/,
									UINT uIDCaption /*= 0*/,
									UINT uTitleID /*= 0*/,
									UINT uSubtitleID /*= 0*/)
	: CNGPropertyPage_BASE(uID, uIDCaption, uTitleID, uSubtitleID)
{
	m_pDoc				= NULL;
	m_pServer			= NULL;

	m_bLockCtrlUpdates	= false;
	m_bModified			= false;

	//{{AFX_DATA_INIT(CNGPropertyPage)
	//}}AFX_DATA_INIT
}


CNGPropertyPage::CNGPropertyPage(	LPCTSTR pszCaption,
									UINT uIDCaption /*= 0*/,
									UINT uTitleID /*= 0*/,
									UINT uSubtitleID /*= 0*/)
	: CNGPropertyPage_BASE(pszCaption, uIDCaption, uTitleID, uSubtitleID)
{
	m_pDoc				= NULL;
	m_pServer			= NULL;

	m_bLockCtrlUpdates	= false;
}


CNGPropertyPage::~CNGPropertyPage(void)
{
}


void CNGPropertyPage::DoDataExchange(CDataExchange* pDX)
{
	CNGPropertyPage_BASE::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CNGPropertyPage)
	//}}AFX_DATA_MAP

	// These are out of Classwizard's grasp as it can't handle the syntax
}


BEGIN_MESSAGE_MAP(CNGPropertyPage, CNGPropertyPage_BASE)
	//{{AFX_MSG_MAP(CNGPropertyPage)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


// Override to initialise data BEFORE the screen controls
BOOL CNGPropertyPage::OnInitDialog(void)
{
	OnInitData();

	return CNGPropertyPage_BASE::OnInitDialog();
}


void CNGPropertyPage::OnInitData(void)
{
	if (IsWindow(m_hWnd))
	{
		UpdateData(FALSE);
	}
}


//	Override the default message processing to try to figure out
//	when the value of a control has changed, so that we can enable
//	the Apply button
//
//	NOTES:
//		1.	UpdateData(TRUE) is automatically called if the control value
//			could have changed, and DDV validation succeeds
//
//		2.	The detection of changed controls is not perfect, and hence the Apply
//			button could be erroneously enabled under certain circumstances.
//
BOOL CNGPropertyPage::OnCommand(WPARAM wParam, LPARAM lParam) 
{
	BOOL bResult = FALSE;

	// Crack message parameters
	// This bit was nicked from CPropertySheet::OnCommand()
	UINT uID		= LOWORD(wParam);
	HWND hWnd		= (HWND)lParam;
	int nCode		= HIWORD(wParam);

	UINT uCtrlID	= uID;						// We'll need this later

	// If the command notification was a control value change
	// which could be linked up via DDX update the DDX member
	// vars and send a WM_COMMAND notification on the affected
	// control (this allows a generic ON_COMMAND handler to be
	// used, irrespective of the type of the control)

	if (hWnd != NULL)
	{
		// Catch ENTER keypresses on a control
		// and convert to a CN_COMMAND call for that control
		// This allows the control focused when ENTER was pressed to receive
		// the change notification...
		if (uID == IDOK)
		{
			// Walk up from the focused window until we find a valid control
			HWND hWndFocusCtrl = ::GetFocus();
			while ( (hWndFocusCtrl != NULL) && (::GetParent(hWndFocusCtrl) != m_hWnd) )
			{
				hWndFocusCtrl=::GetParent(hWndFocusCtrl);
			}
			
			// If the focused control is the one we orignially received the 
			// notification from it must be the OK button itself, so
			// let it through unchanged
			if ( (hWndFocusCtrl == NULL) || (hWndFocusCtrl == hWnd) )
			{
				return CNGPropertyPage_BASE::OnCommand(wParam, lParam);
			}

			// CN_COMMAND notification fires on the focused control
			hWnd = hWndFocusCtrl;
		}	
		else if (nCode == CN_COMMAND)
		{
			uCtrlID = ::GetCtrlID(this, hWnd);
			hWnd = ::GetDlgItem(GetSafeHwnd(), uCtrlID);
			ASSERT(uCtrlID != 0);
			ASSERT(uCtrlID != 65535);

			// If the ID of the control in the message isn't the same as that
			// of the control we've identified, pass it through
			// (this can happen with radio buttons)
			if (uID != uCtrlID)
			{
				bResult = CNGPropertyPage_BASE::OnCommand(wParam, lParam);
			}
		}
		else
		{
			// All non-button controls end up here...
			bResult = CNGPropertyPage_BASE::OnCommand(wParam, lParam);
		}
		if (::IsCtrlValueChanging(this, uCtrlID, hWnd, nCode) && !m_bLockCtrlUpdates)
		{
			bResult = OnCtrlValueChanging(uCtrlID, hWnd, nCode);
		}
	}
	else
	{
		bResult = CNGPropertyPage_BASE::OnCommand(wParam, lParam);
	}
	return bResult;
}


// Called when the value of a control has been changed
BOOL CNGPropertyPage::OnCtrlValueChanging(UINT uCtrlID, HWND hWnd, int nCode)
{
	UNREFERENCED_PARAMETER(hWnd);
	UNREFERENCED_PARAMETER(nCode);

	BOOL bResult = UpdateData(TRUE);			// Try to retrieve dialog data via DoDataExchange()
	if (bResult)
	{											// Validation succeeded...
		OnCmdMsg(uCtrlID, 0, NULL, NULL);		// ...so generate a WM_COMMAND notification...
		SetModified();							// ...and mark the page as changed
	}
	return bResult;
}


BOOL CNGPropertyPage::OnApply(void)
{
	ASSERT_VALID(this);
	
	BOOL bResult = CNGPropertyPage_BASE::OnApply();

	m_bModified			= FALSE;

	return bResult;
}


// Override to let the parent know when a page becomes active
BOOL CNGPropertyPage::OnSetActive(void)
{
	CNGPropertySheet* pParent = DYNAMIC_DOWNCAST(CNGPropertySheet, GetParent());
	if (pParent != NULL)
	{
		pParent->OnSetActive(this);
	}
	return CNGPropertyPage_BASE::OnSetActive();
}


// Override to:
//	1.	Let the parent know when a page becomes inactive
//	2.	Prevent reentrant calls to UpdateData() via OnCommand()
BOOL CNGPropertyPage::OnKillActive(void)
{
	ASSERT_VALID(this);

	m_bLockCtrlUpdates = true;

	CNGPropertySheet* pParent = DYNAMIC_DOWNCAST(CNGPropertySheet, GetParent());
	if (pParent != NULL)
	{
		pParent->OnKillActive(this);
	}
	BOOL bResult = CNGPropertyPage_BASE::OnKillActive();
	m_bLockCtrlUpdates = false;

	return bResult;
}


/////////////////////////////////////////////////////////////////////////////
// CNGPropertyPage operations

BOOL CNGPropertyPage::SetServer(CObject* pServer)
{
	m_pServer = pServer;
	ASSERT_VALID(m_pServer);

	return (m_pServer != NULL);
}


BOOL CNGPropertyPage::SetDocument(CDocument* pDocument)
{
	m_pDoc = pDocument;
	ASSERT_VALID(m_pDoc);

	return (m_pDoc != NULL);
}


void CNGPropertyPage::SetModified(BOOL bChanged /*= TRUE*/)
{
	m_bModified = bChanged;
	CNGPropertyPage_BASE::SetModified(bChanged);
}


BOOL CNGPropertyPage::EnableDlgControl(UINT uID, BOOL bEnable)
{
	CWnd* pWnd = GetDlgItem(uID);
    if (NULL != pWnd)
	{
		pWnd->EnableWindow(bEnable);

		return TRUE;
	}
	ASSERT(FALSE);
	return FALSE;
}


BOOL CNGPropertyPage::ShowDlgControl(UINT uID, BOOL bShow)
{
	CWnd* pWnd = GetDlgItem(uID);
    if (NULL != pWnd)
	{
		pWnd->ShowWindow(bShow);
		return TRUE;
	}
	ASSERT(FALSE);
	return FALSE;
}
	

BOOL CNGPropertyPage::SetReadOnly(UINT uID, BOOL bReadOnly /*= TRUE*/)
{
	CEdit* pCtrl = static_cast<CEdit*>( GetDlgItem(uID) );
    if (NULL != pCtrl)
	{
		pCtrl->SetReadOnly(bReadOnly);
		return TRUE;
	}
	ASSERT(FALSE);
	return FALSE;
}


BOOL CNGPropertyPage::UpdateData(BOOL bSaveAndValidate /*= TRUE*/)
{
	if (IsWindow(m_hWnd))
	{
		return CNGPropertyPage_BASE::UpdateData(bSaveAndValidate);
	}
	return FALSE;
}



/////////////////////////////////////////////////////////////////////////////
// CNGPropertyPage message handlers




/////////////////////////////////////////////////////////////////////////////
// CNGPropertySheet

IMPLEMENT_DYNAMIC(CNGPropertySheet, CNGPropertySheet_BASE)

CNGPropertySheet::CNGPropertySheet(UINT uIDCaption, CWnd* pParentWnd, UINT iSelectPage)
	:CNGPropertySheet_BASE(uIDCaption, pParentWnd, iSelectPage)
{
	m_rectInitialPosition.SetRectEmpty();
	m_rectPosition.SetRectEmpty();

	EnableStackedTabs(FALSE);
}


CNGPropertySheet::CNGPropertySheet(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)
	:CNGPropertySheet_BASE(pszCaption, pParentWnd, iSelectPage)
{
	m_rectInitialPosition.SetRectEmpty();
	m_rectPosition.SetRectEmpty();

	EnableStackedTabs(FALSE);
}


CNGPropertySheet::~CNGPropertySheet(void)
{
}


BEGIN_MESSAGE_MAP(CNGPropertySheet, CNGPropertySheet_BASE)
	//{{AFX_MSG_MAP(CNGPropertySheet)
	ON_WM_MOVE()
	ON_WM_HELPINFO()
	ON_WM_CONTEXTMENU()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CNGPropertySheet overrides

BOOL CNGPropertySheet::OnInitDialog(void)
{
	// Add a'?' button to the title bar for context help support
	ModifyStyleEx(0, WS_EX_CONTEXTHELP);

	BOOL bResult = CNGPropertySheet_BASE::OnInitDialog();
	
	SetInitialPosition();
	
	return bResult;
}


void CNGPropertySheet::OnSetActive(CPropertyPage* pPage)
{
	ASSERT_VALID(this);

	UNREFERENCED_PARAMETER(pPage);
}



void CNGPropertySheet::OnKillActive(CPropertyPage* pPage)
{
	ASSERT_VALID(this);

	UNREFERENCED_PARAMETER(pPage);
}

BOOL CNGPropertySheet::OnCommand(WPARAM wParam, LPARAM lParam) 
{
	BOOL bResult = FALSE;

	// Crack message parameters
	// This bit was nicked from CPropertySheet::OnCommand()
	UINT uID		= LOWORD(wParam);
	HWND hWnd		= (HWND)lParam;
	//int nCode		= HIWORD(wParam);

	//UINT uCtrlID	= uID;						// We'll need this later

	// If the command notification was a control value change
	// which could be linked up via DDX update the DDX member
	// vars and send a WM_COMMAND notification on the affected
	// control (this allows a generic ON_COMMAND handler to be
	// used, irrespective of the type of the control)

	if (hWnd != NULL)
	{
		// Catch ENTER keypresses on a control
		// and convert to a CN_COMMAND call for that control
		// This allows the control focused when ENTER was pressed to receive
		// the change notification...
		if (uID == IDOK)
		{
			// Walk up from the focused window until we find a valid control
			HWND hWndFocusCtrl = ::GetFocus();
			while ( (hWndFocusCtrl != NULL) && (::GetParent(hWndFocusCtrl) != m_hWnd) )
			{
				hWndFocusCtrl =::GetParent(hWndFocusCtrl);
			}
			
			// If the focused control is the one we orignially received the 
			// notification from it must be the OK button itself, so
			// let it through unchanged
			if ( (hWndFocusCtrl == NULL) || (hWndFocusCtrl == hWnd) )
			{
				return CNGPropertySheet_BASE::OnCommand(wParam, lParam);
			}

			// Fire a CN_COMMAND notification on the focused control
			hWnd = hWndFocusCtrl;

			wParam = MAKEWPARAM(uID, CN_COMMAND);
			lParam = (LPARAM)hWnd;
			::SendMessage(hWnd, WM_COMMAND, wParam, lParam);

			// Move the focus to the next control
			::PostMessage(m_hWnd, WM_NEXTDLGCTL, 0, 0L);

			bResult = TRUE;			// Prevent sheet from closing
		}
		else
		{
			bResult = CNGPropertySheet_BASE::OnCommand(wParam, lParam);
		}
	}
	return bResult;
}




/////////////////////////////////////////////////////////////////////////////
// CNGPropertySheet operations

BOOL CNGPropertySheet::EnableDlgControl(UINT uID, BOOL bEnable)
{
	CWnd* pWnd = GetDlgItem(uID);
    if (NULL != pWnd)
	{
		pWnd->EnableWindow(bEnable);

		return TRUE;
	}
	ASSERT(FALSE);
	return FALSE;
}


BOOL CNGPropertySheet::ShowDlgControl(UINT uID, BOOL bShow)
{
	CWnd* pWnd = GetDlgItem(uID);
    if (NULL != pWnd)
	{
		pWnd->ShowWindow(bShow);
		return TRUE;
	}
	ASSERT(FALSE);
	return FALSE;
}
	

BOOL CNGPropertySheet::UpdateData(BOOL bSaveAndValidate /*= TRUE*/)
{
	if (IsWindow(m_hWnd))
	{
		return CNGPropertySheet_BASE::UpdateData(bSaveAndValidate);
	}
	return FALSE;
}


/////////////////////////////////////////////////////////////////////////////
// CNGPropertySheet message handlers

void CNGPropertySheet::OnMove(int x, int y) 
{
	CNGPropertySheet_BASE::OnMove(x, y);
	
	GetWindowRect(&m_rectPosition);
}


/////////////////////////////////////////////////////////////////////////////
// CNGPropertySheet Help Support

// Context help generated by pressing F1 or using the "?" button on the title
// bar of the property sheet.
BOOL CNGPropertySheet::OnHelpInfo(HELPINFO* pHelpInfo) 
{
	if (pHelpInfo->iContextType == HELPINFO_WINDOW)
	{
		// Launch WinHelp or HtmlHelp
		// The HELP_WM_HELP flag brings up pop-up help and expects an array 
		// of DWORD pairs of the control ID and the context help ID
		UINT uCtrlID = pHelpInfo->iCtrlId;
		if ( (uCtrlID != 0) && (uCtrlID != IDC_STATIC) )
		{
			CTRLID_HELPID_PAIR dwHelpIds(	uCtrlID,
											uCtrlID + HID_BASE_CONTROL);

			::WinHelp(	(HWND)pHelpInfo->hItemHandle,
						AfxGetApp()->m_pszHelpFilePath,	
						HELP_WM_HELP,
						(DWORD)&dwHelpIds);
		}
	}
	return TRUE;
}


//	Context help generated by right clicking the control and selecting the
//	"Whats this ?" menu.
void CNGPropertySheet::OnContextMenu(CWnd* pWnd, CPoint point) 
{
 	// The title bar has a system provided context menu which is displayed for
	// a right mouse button up
	if (HTCLIENT == OnNcHitTest(point))
	{
		//TRACE("OnContextMenu - HTCIENT\n");
		CWnd* pChildWnd;
		if (pWnd == this) //if the control on the Sheet is disabled
		{
			// get the hWnd of the disabled child window
			ScreenToClient(&point);
			pChildWnd = ChildWindowFromPoint(point, CWP_ALL);
		}
		else
		{
			// for OK, Cancel and Apply (if enabled) buttons
			pChildWnd = pWnd;
		}

		// Launch WinHelp or HtmlHelp
		// The HELP_CONTEXTMENU flag brings up the "Whats this ?" menu and selecting
		// the menu in turn brings up the pop-up help. It expects an array 
		// of DWORD pairs of the control ID and the context help ID
		
		UINT uCtrlID = pChildWnd->GetDlgCtrlID();
		if ( (uCtrlID != 0) && (uCtrlID != IDC_STATIC) )
		{
			CTRLID_HELPID_PAIR dwHelpIds(	uCtrlID,
											uCtrlID + HID_BASE_CONTROL);

			::WinHelp(	(HWND)pWnd->m_hWnd,
						AfxGetApp()->m_pszHelpFilePath,
						HELP_CONTEXTMENU,
						(DWORD)&dwHelpIds);
		}
	}
}


/////////////////////////////////////////////////////////////////////////////
// Implementation

void CNGPropertySheet::SetInitialPosition(void)
{
	// If we've got a position from last time use it to position
	// our window this time
	if (!m_rectInitialPosition.IsRectEmpty())
	{
		// As the property sheet is not scaleable at the moment we need
		// to make sure we change the size of m_rectPosition to match
		// the size of the window as it is now (remember that adding or
		// deleting pages between invokations may change the size)
		m_rectInitialPosition.BottomRight() =
							CPoint(	m_rectInitialPosition.left + m_rectPosition.Width(),
									m_rectInitialPosition.top + m_rectPosition.Height() );


		MoveWindow(m_rectInitialPosition);
	}
}


By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder Riverblade Limited
United Kingdom United Kingdom
I haven't always written software for a living. When I graduated from Surrey University in 1989, it was with an Electronic Engineering degree, but unfortunately that never really gave me the opportunity to do anything particularly interesting (with the possible exception of designing Darth Vader's Codpiece * for the UK Army in 1990).
    * Also known as the Standard Army Bootswitch. But that's another story...
Since the opportunity arose to lead a software team developing C++ software for Avionic Test Systems in 1996, I've not looked back. More recently I've been involved in the development of subsea acoustic navigation systems, digital TV broadcast systems, port security/tracking systems, and most recently software development tools with my own company, Riverblade Ltd.

One of my personal specialities is IDE plug-in development. ResOrg was my first attempt at a plug-in, but my day to day work is with Visual Lint, an interactive code analysis tool environment with works within the Visual Studio and Eclipse IDEs or on build servers.

I love lots of things, but particularly music, photography and anything connected with history or engineering. I despise ignorant, intolerant and obstructive people - and it shows...I can be a bolshy cow if you wind me up the wrong way...Laugh | :laugh:

I'm currently based 15 minutes walk from the beach in Bournemouth on the south coast of England. Since I moved here I've grown to love the place - even if it is full of grockles in Summer!

Comments and Discussions