Click here to Skip to main content
15,860,972 members
Articles / Desktop Programming / MFC
Article

CWnd Helper Class

Rate me:
Please Sign up or sign in to vote.
4.98/5 (16 votes)
6 Dec 20026 min read 85.1K   1.2K   58   3
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 <A href="#Save">Save</A>(CWnd* pWnd, LPCTSTR szWindowName = NULL);
static void <A href="#Restore">Restore</A>(CWnd* pWnd, LPCTSTR szWindowName = NULL, 
unsigned uFlags = Position);
	
static void <A href="#ChangeBy">ChangeBy</A>(CWnd* pWnd, int nPixels, unsigned uFlags);
static void <A href="#AlignControl">AlignControl</A>(CWnd* pWnd, 
unsigned uCtrlToAlign, unsigned uCtrlToAlignAgainst, 
	              unsigned uFlags, int nOffset = 0);
static void <A href="#OrganizeSequentialControls">OrganizeSequentialControls</A>(CWnd* pWnd,   unsigned uFirstCtrl, 
unsigned uLastCtrl, int nDistance, unsigned uFlags = CAMSWnd::Y);

static int  <A href="#GetTextExtent">GetTextExtent</A>(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 <A href="#FocusHolder">FocusHolder</A>
{
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 <A href="#Disabler">Disabler</A>
{
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 <A href="#Hook">Hook</A>
{
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 <A href="#PlacementHook">PlacementHook</A> : 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


Written By
Web Developer
United States United States
I've done extensive work with C++, MFC, COM, and ATL on the Windows side. On the Web side, I've worked with VB, ASP, JavaScript, and COM+. I've also been involved with server-side Java, which includes JSP, Servlets, and EJB, and more recently with ASP.NET/C#.

Comments and Discussions

 
GeneralLicensing for commercial application Pin
will626219-Oct-10 5:08
will626219-Oct-10 5:08 
GeneralThis is... GREAT ! Pin
Kochise12-Mar-04 2:34
Kochise12-Mar-04 2:34 
GeneralProblem in the AlignControls function Pin
Cathy24-Apr-02 14:12
Cathy24-Apr-02 14:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.