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
{
Left = 0x0001,
Right = 0x0002,
Top = 0x0004,
Bottom = 0x0008,
X = 0x0003,
Y = 0x000C,
Position = 0x000F,
Width = 0x0010,
Height = 0x0020,
Size = 0x0030,
Both = 0x003F,
State = 0x0040,
All = 0x007F,
NoRedraw = 0x1000
};
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">FocusHolder
{
public:
FocusHolder(CWnd* pWnd);
FocusHolder(CWnd& wnd);
~FocusHolder();
private:
CWnd* m_pWnd;
};
class "#Disabler">Disabler
{
public:
Disabler(CWnd* pWnd);
Disabler(CWnd& wnd);
~Disabler();
private:
CWnd* m_pWnd;
};
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();
private:
HWND m_hWnd;
WNDPROC m_pWndProcOld;
private:
static CMap<HWND, HWND&, Hook*, Hook*&> m_mapHookedWindows;
};
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;
unsigned m_uFlags;
};
};
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:
CAMSWnd::ChangeBy(GetDlgItem(IDOK), 100, CAMSWnd::Y);
CAMSWnd::ChangeBy(GetDlgItem(IDCANCEL), 100, CAMSWnd::Y);
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:
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()
{
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()
{
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:
class CMyDialog : public CDialog, CAMSWnd::PlacementHook
{
...
};
BOOL CTestTextWriterDlg::OnInitDialog()
{
CDialog::OnInitDialog();
SetLastPosition(this);
return TRUE;
}
Usage
To use the CAMSWnd
class inside your project:
- Add amsWnd.cpp and amsWnd.h to your project.
- Include amsWnd.h inside your sources. I recommend including it inside stdafx.h so that it's only done in one place.
- Use its static members and classes anywhere you need them.
- Enjoy!