Saturday, June 5, 2010

Subclassing child window using WTL

To create custom control  you may use WM_DRAW_ITEM but this message you will receive in DLGPROC
So you have to draw control not in his own procedure but in parent one.

To resolve this complexity you can use subclassing with the child control.
If you are using WTL - all need to do is create class dediven not from standard WTL controls with other template parameter

For example for ATL::CListBox use

class CListBoxEx;

typedef CWindowImpl< CListBoxEx, CWindow, CControlWinTraits> CMyListBox;
template <>
HWND CListBoxT::Create(HWND hWndParent, ATL::_U_RECT rect, LPCTSTR szWindowName,
DWORD dwStyle, DWORD dwExStyle,
ATL::_U_MENUorID MenuOrID, LPVOID lpCreateParam)
{
      return CMyListBox::Creat
e( hWndParent
                               , rect
                               , szWindowName
                               , dwStyle
                               , dwExStyle
                               , MenuOrID
                               , lpCreateParam);
}

template <>
CListBoxT::CListBoxT(HWND hParentHwnd)
:CMyListBox()
{
   if (::IsWindow(hParentHwnd))
   {
      CMyListBox::SetParent(hParentHwnd);
   }
}


class CListBoxEx :
public CListBoxT
{
...
}
 

This code allows you create a control and using Subclass-window replace WNDPROC for that control class
Now we can create or subclass a control


class CList_boxDialog : public CAppStdDialogResizeImpl,
public CUpdateUI,
public CMessageFilter, public CIdleHandler
{
... 
LRESULT OnInitDialog(UINT uMsg, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) 
{
    ..
    CWindow wnd = GetDlgItem(IDC_LIST_ADAPTERS);  
    mListBox.SubclassWindow(wnd);
    mListBox.SetParent(m_hWnd);

    mListBox.ResetContent();
    mListBox.AddString(_T("First"));
    mListBox.AddString(_T("Second"));
    ..
}

..
CListBoxEx mListBox;
} 

Code above get controls window handle and WTL replace controls window procedure


You should allow all unhandled messages to pass to old WNDPROC to do it just return 'false' from
ProcessWindowMessage()  

in CListBoxEx class:


BEGIN_MSG_MAP(CListBoxEx)
   MESSAGE_HANDLER(OCM_DRAWITEM, OnDrawItem)
   MESSAGE_HANDLER(OCM_MESUREITEM, OnMesureItem)
END_MSG_MAP()


Next step is message reflection

One of the most difficulties is to follow code in templates .. 
In your CDialog class you need to reflect all messages so they will be handled in child control class
to reflect all unhanded messages use

class CMyDialog;
BEGIN_MSG_MAP(CMyDialog)

    REFLECT_NOTIFICATIONS()
    CHAIN_MSG_MAP(CAppStdDialogResizeImpl<
CMyDialog>)END_MSG_MAP()

Now you should be able to receive OCM_XXX and handle them in child control

This trick allow you to hold code in one place and increase portability and reduce time to create applications
As usual people don't create GUI in C++  even for Windows CE, Mobile but who knows ;)