CContainedWindow
The Parent
Handling the Messages of the Child
A CWindow-based object lets an existing
window class handle its messages. This is useful for wrapping child
windows. A CWindowImpl-based object handles its own
messages via a message map. This is handy for creating parent
windows. Often, you want to let a parent window handle messages for
a child window. Objects of another ATL window class,
CContainedWindow, let the parent window handle the
messages and let an existing window class handle the messages the
parent doesn't. This centralizes message handling in a parent
window. The parent window can either create an instance of the
child window class or let someone else (such as the dialog manager)
create it and then subclass it later (I discuss subclassing soon).
Either way, the messages of the child window are routed through the
message map of the parent window. How does the parent window
discern its own messages from those of one or more children? You
guessed it: alternate message maps. Each CContainedWindow
gets a message map ID, and its messages are routed to that
alternate part of the parent's message map.
To support the creation of both contained and
subclassed windows, CContainedWindow is defined like
this:
template <class TBase, class TWinTraits>
class CContainedWindowT : public Tbase {
public:
CWndProcThunk m_thunk;
LPCTSTR m_lpszClassName;
WNDPROC m_pfnSuperWindowProc;
CMessageMap* m_pObject;
DWORD m_dwMsgMapID;
const _ATL_MSG* m_pCurrentMsg;
// If you use this constructor you must supply
// the Window Class Name, Object* and Message Map ID
// Later to the Create call
CContainedWindowT();
CContainedWindowT(LPTSTR lpszClassName, CMessageMap* pObject,
DWORD dwMsgMapID = 0);
CContainedWindowT(CMessageMap* pObject, DWORD dwMsgMapID = 0);
void SwitchMessageMap(DWORD dwMsgMapID);
const _ATL_MSG* GetCurrentMessage() const;
LRESULT DefWindowProc();
LRESULT DefWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK
StartWindowProc(HWND hWnd, UINT uMsg,
WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK
WindowProc(HWND hWnd, UINT uMsg,
WPARAM wParam, LPARAM lParam);
ATOM RegisterWndSuperclass();
HWND Create(HWND hWndParent, _U_RECT rect,
LPCTSTR szWindowName = NULL,
DWORD dwStyle = 0, DWORD dwExStyle = 0,
_U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL);
HWND Create(CMessageMap* pObject, DWORD dwMsgMapID,
HWND hWndParent,
_U_RECT rect, LPCTSTR szWindowName = NULL,
DWORD dwStyle = 0, DWORD dwExStyle = 0,
_U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL);
HWND Create(LPCTSTR lpszClassName, CMessageMap* pObject,
DWORD dwMsgMapID, HWND hWndParent, _U_RECT rect,
LPCTSTR szWindowName = NULL,
DWORD dwStyle = 0, DWORD dwExStyle = 0,
_U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL);
BOOL SubclassWindow(HWND hWnd);
// Use only if you want to subclass before window
// is destroyed, WindowProc will automatically subclass
// when window goes away
HWND UnsubclassWindow(BOOL bForce = FALSE);
LRESULT ReflectNotifications(UINT uMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled);
};
typedef CContainedWindowT<CWindow> CContainedWindow;
Notice that
CContainedWindow does not derive from
CWindowImpl, nor does it derive from CMessageMap.
CContainedWindow objects do not have their own message
map. Instead, the WindowProc static member function of
CContainedWindow routes messages to the parent window. The
specific message map ID is provided either to the constructor or to
the Create function.
Creating Contained
Windows
Notice also the various constructors and
Create functions: Some take the name of the window class,
and some do not. If you want a parent window to handle a child
window's messages, you have quite a bit of flexibility. For
example, to morph the letter box example to create an edit control
that accepts only letters, CContainedWindow would be used
like this:
class CMainWindow :
public CWindowImpl<CMainWindow, CWindow, CMainWindowTraits> {
public:
...
BEGIN_MSG_MAP(CMainWindow)
...
// Handle the child edit control's messages
ALT_MSG_MAP(1)
MESSAGE_HANDLER(WM_CHAR, OnEditChar)
END_MSG_MAP()
LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&) {
// Create the contained window, routing its messages to us
if( m_edit.Create("edit", this, 1, m_hWnd,
&CWindow::rcDefault) ) {
return 0;
}
return -1;
}
// Let the child edit control receive only letters
LRESULT OnEditChar(UINT, WPARAM wparam, LPARAM,
BOOL& bHandled) {
if( isalpha((TCHAR)wparam) ) bHandled = FALSE;
else MessageBeep(0xFFFFFFFF);
return 0;
}
...
private:
CContainedWindow m_edit;
};
When the main window is created, this code
associates an HWND with the CContainedWindow
object m_edit by creating a new edit control, identified
because of the window class passed as the first parameter to
Create. The second parameter is a CMessageMap
pointer for where the contained windows messages will be routed.
This parameter is most often the parent window, but it doesn't have
to be.
To separate the parent's messages from the
child's, the parent's message map is divided into two parts, the
main part and one alternate part. The ID of the alternate part of
the message map is passed as the third parameter to
Create.
Finally, to filter all characters but the
letters sent to the contained edit control, the WM_CHAR
handler passes through only the WM_CHAR messages that
contain letters. By setting bHandled to FALSE,
the parent window indicates to the CContainedWindow
WndProc that it should keep looking for a handler for this
message. Eventually, after the message map has been exhausted
(including any chaining that might be going on), the
WindowProc passes the message onto the edit control's
normal WndProc. For nonletters, the WM_CHAR
handler leaves bHandled set to trUE (the
default), which stops the message from going anywhere else and
stops the child edit control from seeing any WM_CHAR
messages without letters in them. As far as the child edit control
is concerned, the user entered only letters.
Subclassing
Contained Windows
CContainedWindow, as previously
described, works well if you are the one to call Create.
However, in several important scenarios, you want to hook the
message processing of a window that has already been createdfor
example, if the dialog manager has already created an edit control.
In this case, to get hold of the window's messages, you must
subclass
it. Previously in this chapter, I described superclassing as the
Windows version of inheritance for window classes. Subclassing is a
more modest and frequently used technique. Instead of creating a
whole new window class, with subclassing, we merely hijack the
messages of a single window. Subclassing is accomplished by
creating a window of a certain class and replacing its
WndProc with our own using
SetWindowLong(GWL_WNDPROC). The replacement
WndProc gets all the messages first and can decide whether
to let the original WndProc handle it as well. If you
think of superclassing as specialization of a class, subclassing is
specialization of a single instance. Subclassing is usually
performed on child windows, such as an edit box that the dialog
wants to restrict to letters only. The dialog would subclass the
child edit control during WM_INITDIALOG and handle
WM_CHAR messages, throwing out any that weren't
suitable.
For example, subclassing an edit control in a
dialog would look like this:
class CLettersDlg : public CDialogImpl<CLettersDlg> {
public:
// Set the CMessageMap* and the message map id
CLettersDlg() : m_edit(this, 1) {}
BEGIN_MSG_MAP(CLettersDlg)
...
ALT_MSG_MAP(1)
MESSAGE_HANDLER(WM_CHAR, OnEditChar)
END_MSG_MAP()
enum { IDD = IDD_LETTERS_ONLY };
LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) {
// Subclass the existing child edit control
m_edit.SubclassWindow(GetDlgItem(IDC_EDIT));
return 1; // Let the dialog manager set the initial focus
}
...
private:
CContainedWindow m_edit;
};
In this case, because the sample doesn't call
Create, it has to pass the CMessageMap pointer
and the message map ID of the child edit control in the constructor
using the member initialization list syntax. When the contained
window knows who to route the messages to and which part of the
message map to route to, it needs to know only the HWND of
the window to subclass. It gets this in the SubclassWindow
call in the WM_INITDIALOG handler. The
CContainedWindow implementation of SubclassWindow
shows how subclassing is performed:
template <class TBase, class TWinTraits>
BOOL CContainedWindowT<TBase, TWinTraits>::SubclassWindow(
HWND hWnd) {
BOOL result;
result = m_thunk.Init(WindowProc, this);
if (result == FALSE) {
return result;
}
WNDPROC pProc = m_thunk.GetWNDPROC();
WNDPROC pfnWndProc = (WNDPROC)::SetWindowLongPtr(hWnd,
GWLP_WNDPROC, (LONG_PTR)pProc);
if(pfnWndProc == NULL)
return FALSE;
m_pfnSuperWindowProc = pfnWndProc;
m_hWnd = hWnd;
return TRUE;
}
The
important part is the call to SetWindowLong passing
GWL_WNDPROC. This replaces the current window object's
WndProc with an ATL thunking version that routes messages
to the container. It also returns the existing WndProc,
which CContainedWindow caches to call for any messages
that the container doesn't handle.
Containing the
Window Control Wrappers
After being introduced to the ATL Windows
control wrapper classes, such as CEdit and
CListBox, you might dread window containment. If
CContainedWindow derives from CWindow, where do
all the nifty inline wrappers functions come from? Never fear, ATL
is here. As you've already seen, CContainedWindow is
really just a type definition for the CContainedWindowT
template class. One of the parameters is a windows traits class,
which doesn't help you. The other, however, is the name of the base
class. CContainedWindow uses CWindow as the base
for CContaintedWindowT, but there's no reason you have to.
By using one of the ATL Windows control wrapper classes instead,
you can have a contained window that also has all the wrapper
functions. For example, we can change the type of the
m_edit variable like this:
CContainedWindowT<ATLControls::CEdit> m_edit;
This technique is especially handy when you're
using Create instead of SubclassWindow. With
Create, you have to provide the name of the window class.
If you call Create without a window class, the
CContainedWindow object attempts to acquire a window class
by calling the base class function GetWndClassName, which
CWindow implements like this:
static LPCTSTR CWindow::GetWndClassName()
{ return NULL; }
However, each of the ATL window control wrappers
overrides this function to provide its window class name:
static LPCTSTR CEditT::GetWndClassName()
{ return _T("EDIT"); }
Now, when creating an instance of one of the
contained window wrapper classes, you don't have to dig through the
documentation to figure out the class name of your favorite
intrinsic window class; you can simply call Create:
class CMainWindow : public CWindowImpl<...> {
...
LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&) {
// Window class name provided by base class
if( m_edit.Create(this, 1, m_hWnd, &CWindow::rcDefault) ) {
// Now we can use methods of CEditT
m_edit.InsertText( 0, _T("Here's some text") );
return 0;
}
return -1;
}
...
private:
CContainedWindowT<ATLControls::CEdit> m_edit;
};
|