Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

CWnd Helper Class

0.00/5 (No votes)
6 Dec 2002 1  
Class with static functions and nested classes to make working with CWnd-derived objects easier
Introduction
Class Definition
Member Functions
Nested Classes
Usage

Over the years I've worked with many dialog-based applications where I've needed to perform the same type of tasks:

  • Save the dialog's position and/or size when it is closed so it can be shown the same way next time.
  • Change a control's position and/or size by a number of pixels.
  • Align several controls with respect to one.
  • Retrieve the number of pixels a given piece of text will occupy when displayed on a window.
  • Ensure that a control gets the focus when a function exits.
  • Disable a control/window temporarily while some task is performed.
  • Hook into one or more messages to perform a particular task.

These are highly useful tasks which the CWnd class unfortunately lacks, so initially I would write member functions and classes to do these tasks again and again - lots of copy and pasting.  After a while I decided that it was time to create a class for these. 

Ideally these tasks belong in a base class such as CWnd, but changing MFC's code is simply not an option.  So the next best thing: create a helper class and add these tasks to it as static members or nested classes.  That's what CAMSWnd is - a simple namespace-style class with static functions and nested classes that work with a CWnd object passed individually to each one of them.

Class Definition

Here's the complete definition of CAMSWnd:

class CAMSWnd
{
public:
 enum Flags	// Bit values passed to the uFlags parameter of the functions below.

 { 
 Left	   = 0x0001,	// left coordinate

 Right	   = 0x0002,	// right coordinate

 Top        = 0x0004,	// top coordinate

 Bottom	   = 0x0008,	// bottom coordinate

 X 	   = 0x0003,	// horizontal coordinates (left and right)

 Y          = 0x000C,	// vertical coordinates (top and bottom)

 Position   = 0x000F,	// both the horizontal and vertical coordinates

 Width 	   = 0x0010,	// the window's width 

 Height	   = 0x0020,	// the window's height

 Size	   = 0x0030,	// the window's width and height

 Both	   = 0x003F,	// the window's position and size

 State	   = 0x0040,	// the window's state (minimized, maximized, etc.)

 All	   = 0x007F,	// the window's position, size, and state		

 NoRedraw   = 0x1000	// the window is not repainted

 };

static void "#Save">Save(CWnd* pWnd, LPCTSTR szWindowName = NULL);
static void "#Restore">Restore(CWnd* pWnd, LPCTSTR szWindowName = NULL, 
unsigned uFlags = Position);
	
static void "#ChangeBy">ChangeBy(CWnd* pWnd, int nPixels, unsigned uFlags);
static void "#AlignControl">AlignControl(CWnd* pWnd, 
unsigned uCtrlToAlign, unsigned uCtrlToAlignAgainst, 
	              unsigned uFlags, int nOffset = 0);
static void "#OrganizeSequentialControls">OrganizeSequentialControls(CWnd* pWnd,   unsigned uFirstCtrl, 
unsigned uLastCtrl, int nDistance, unsigned uFlags = CAMSWnd::Y);

static int  "#GetTextExtent">GetTextExtent(CWnd* pWnd, const CString& 
            strText, CFont* pFont = NULL);


// Class FocusHolder allows you to save a window object in construction

// whose focus will be set on destruction.

//

class "#FocusHolder">FocusHolder
{
public:
  FocusHolder(CWnd* pWnd);
  FocusHolder(CWnd& wnd);
  ~FocusHolder();

private:
  CWnd* m_pWnd;
};


// Class Disabler allows you to disable a window in construction and 

// reenable it on destruction.

//

class "#Disabler">Disabler
{
public:
  Disabler(CWnd* pWnd);
  Disabler(CWnd& wnd);
  ~Disabler();

private:
  CWnd* m_pWnd;
};
	
	
// Class Hook is an abstract class used for intercepting a given window's messages 

// before they're processed.

//

class "#Hook">Hook
{
public:
  Hook(CWnd* pWnd = NULL);
  virtual ~Hook();
		
  virtual BOOL Open(CWnd* pWnd);
  virtual void Close();

  BOOL IsOpen() const;
  CWnd* GetWindow() const;
  HWND GetHwnd() const;
		
static LRESULT CALLBACK HookedWindowProc(HWND, UINT, WPARAM, LPARAM);

protected:
  virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam) = 0;
  LRESULT Default();		// call this at the end of handler fns

				
private:
  HWND m_hWnd;			// the window being hooked

  WNDPROC	m_pWndProcOld;	// the original window proc

	
private:
// map of windows to hooks

static CMap<HWND, HWND&, Hook*, Hook*&> m_mapHookedWindows;  
};


// Class PlacementHook is used to easily save and restore given window's position

// and size. Simply derive from it and call one of the Set functions inside 

// OnCreate or OnInitDialog.

//

class "#PlacementHook">PlacementHook : private Hook
{
public:
PlacementHook();

void SetLastPositionAndSize(CWnd* pWnd, LPCTSTR szWindowName = NULL);
void SetLastPosition(CWnd* pWnd, LPCTSTR szWindowName = NULL);
void SetLastSize(CWnd* pWnd, LPCTSTR szWindowName = NULL);

protected:
void Open(CWnd* pWnd, LPCTSTR szWindowName);
virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam);

private:
CString m_strWindowName;  // the name of the window (to be saved 

		          // in the registry)

unsigned m_uFlags;        // how the window should be restored

};
};

Member Functions

Below is a description of each of the static member functions in CAMSWnd, although they should be pretty much self-explanatory.

Save

void Save(CWnd* pWnd, LPCTSTR szWindowName = NULL)

Writes pWnd's position, size, and state (minimized, maximized, etc.) to the application's profile (INI file or registry).  If szWindowName is NULL (recommended) the window's title text is used as the key under which to save the information in the INI file or registry.  Thus, if the window text is not always consistent or unique, you should pass it in the szWindowName parameter. 

I've mostly used this function for my dialog boxes.  I've simply added a handler for the WM_DESTROY message and called this function there:

void CMyDialog::OnDestroy() 
{
	CAMSWnd::Save(this);	
	CDialog::OnDestroy();
}

Take a look at the PlacementHook class below for a better alternative.

Note: Remember to call SetRegistryKey inside your CWinApp's InitInstance to make use of the registry instead of an INI file.

Restore

void Restore(CWnd* pWnd, LPCTSTR szWindowName = NULL, unsigned uFlags = Position)

Restores one or more aspects of pWnd depending on the value of uFlags by retrieving the information from the application's profile (INI file or registry) previously written by the Save function (above). 

If szWindowName is NULL (recommended) the window's title text is used as the key under which to retrieve the information from the INI file or registry.  Thus, if the window text is not always consistent or unique, you should pass it in the szWindowName parameter.  Essentially, this parameter should be same as what was passed to the Save function.

I've used this function by calling it inside the WM_INITDIALOG handler to restore the position of my dialog boxes from the registry. 

BOOL CMyDialog::OnInitDialog()
{	
	CDialog::OnInitDialog();

	CAMSWnd::Restore(this);
	return TRUE;
}

Then, I discovered a better way: the PlacementHook class below.

Note: Remember to call SetRegistryKey inside your CWinApp's InitInstance to make use of the registry instead of an INI file.

ChangeBy

void ChangeBy(CWnd* pWnd, int nPixels, unsigned uFlags)

Changes one or more aspects of pWnd by the given number of nPixels, depending on the value of uFlags .  The CAMSWnd::State flag does not apply here, but the CAMSWnd::NoRedraw flag may be used to prevent the window from being redrawn unnecessarily.

This function has come in handy when moving a set of controls as a group:

// Move these two buttons down 100 pixels from their current position

CAMSWnd::ChangeBy(GetDlgItem(IDOK), 100, CAMSWnd::Y);
CAMSWnd::ChangeBy(GetDlgItem(IDCANCEL), 100, CAMSWnd::Y);

// Make the following combo box 20 pixels wider

CAMSWnd::ChangeBy(GetDlgItem(IDC_COMBO1), 20, CAMSWnd::Width);

AlignControl

void AlignControl(CWnd* pWnd, unsigned uCtrlToAlign, unsigned uCtrlToAlignAgainst, 
                 unsigned uFlags, int nOffset = 0)

Aligns the given uCtrlToAlign based on the current position and/or size of the uCtrlToAlignAgainst. Additionally the aligned control may be placed by nOffset pixels away from uCtrlToAlignAgainst.  The uFlags may be any combination of CAMSWnd::Left, CAMSWnd::Right, CAMSWnd::Top, CAMSWnd::Bottom, CAMSWnd::Width, or CAMSWnd::Height.

I've used this function as an alternative to ChangeBy (above), although it has other uses:

// Align both buttons on the same Y position

CAMSWnd::ChangeBy(GetDlgItem(IDOK), 100, CAMSWnd::Y);
CAMSWnd::AlignControl(this, IDCANCEL, IDOK, CAMSWnd::Y);

OrganizeSequentialControls

void OrganizeSequentialControls(CWnd* pWnd, unsigned uFirstCtrl, unsigned uLastCtrl, 
                                int nDistance, unsigned uFlags = CAMSWnd::Y)

Lines up the given set of sequentially numbered controls horizontally (CAMSWnd::X) or vertically (CAMSWnd::Y) by the given nDistance (in pixels).  As a result, every control ends up looking exactly like the first one but is separated by nDistance pixels from its horizontal or vertical position.

I used this function on several large dialog boxes that contained several rows and columns of edit controls, all of which needed to be sized and spaced uniformly. Unfortunately, since DevStudio's editor uses brain-dead dialog units, I had to resort to doing it programmatically.

CAMSWnd::OrganizeSequentialControls(this, IDC_EDIT1, IDC_EDIT5, 13);

GetTextExtent

int GetTextExtent(CWnd* pWnd, const CString& strText, CFont* pFont = NULL)

Determines the number of pixels it takes to display the given strText on the given pWnd's device context.  If pFont is not NULL, it is temporarily selected into the window's device context and used in the calculation.

This function came in handy a while back when I needed to dynamically create a window to show a small piece of text.

Nested Classes

Below is a description of each of the nested classes inside CAMSWnd.

FocusHolder

The FocusHolder class ensures that after a block of code has executed the focus is set to a particular window (such as a control).  You simply construct it by passing it the window you need to set the focus on and "forget about it" - FocusHolder's destructor does the rest.   You may construct it using either a CWnd pointer or reference.

I've used this class mostly in two scenarios: (1) as an edit control with a browse button next to it, and (2) as a list of items with one or more related buttons (Add, Edit, Delete, etc.).  Here's an example of a handler for the Delete button:

void CMyDialog::OnButtonDeleteFile() 
{
        // sets the window to hold focus

	CAMSWnd::FocusHolder focus = m_ctlListFiles;   
	int nSelectedCount = m_ctlListFiles.GetSelectedCount();
	if (nSelectedCount == 0)
		return;

	m_ctlListFiles.DeleteItems();
	UpdateControls();
}

Disabler

This class works very similar to FocusHolder except that it temporarily disables a window on construction and reenables it on destruction.  You may construct it using either a CWnd pointer or reference.

I've found it handy in special cases where the current window needed to be disabled temporarily to give the illusion of modality to a separate pop-up window. 

void CMyDialog::OnDoSomethingSpecial()  
{
        // disable the dialog box while the pop up is shown

	CAMSWnd::Disabler disable = this;   
	PopupSomeWindow();
}

Hook

This is an abstract class that allows you to objectize a particular action whenever one or more windows messages are intercepted.  An instance of a Hook-derived class basically attaches itself to a particular window (via its Open function) and then starts receiving all of the window's messages through its WindowProc virtual function.  The "hooking" is done via the SetWindowLong API, so no special hook DLL is required.  A good example of how this class can be used is the PlacementHook class which follows.

PlacementHook

This class implements the Hook class to provide a great alternative to saving and restoring a window's position and/or size.  You simply derive your dialog or window class from it and then call one of its three member functions (SetLastPositionAndSize, SetLastPosition, or SetLastSize) inside the WM_INITDIALOG or WM_CREATE handler.  That's it! 

Here's how I've used it to save/restore my dialog boxes' positions:

// Inside MyDialog.h


class CMyDialog : public CDialog, CAMSWnd::PlacementHook
{
	...
};
// Inside MyDialog.cpp


BOOL CTestTextWriterDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// Note: window must exist before calling this

	SetLastPosition(this);
	
	return TRUE;
}

Usage

To use the CAMSWnd class inside your project:

  1. Add amsWnd.cpp and amsWnd.h to your project.
  2. Include amsWnd.h inside your sources. I recommend including it inside stdafx.h so that it's only done in one place.
  3. Use its static members and classes anywhere you need them.
  4. Enjoy!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here