previous page
next page

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[11] 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.

[11] For a more complete dissection of Windows subclassing, see Win32 Programming (Addison-Wesley, 1997), by Brent Rector and Joe Newcomer.

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;
};


previous page
next page
Converted from CHM to HTML with chm2web Pro 2.75 (unicode)