Subclassing controls in ATL dialogs using WTL

Environment: VC6 SP3, Win2K (I think)

Introduction

When creating dialogs (i.e. a composite ActiveX control) using ATL it is sometimes necessary to change the behaviour of the embedded Windows controls. It could be something like changing the behaviour of the [RETURN] key to act as the [TAB] key. It could also be only allowing specific keystrokes. When using MFC this is a fairly easy task. The following will show how to do this using a subclass for a WTL class that can do the task. The advantage of using WTL this is to reuse the work done by Microsoft in implementing all the messages as methods. The following example subclasses an edit control but the same techniques can be used with many other controls, like a button control.

The task can be divided into four subtasks

  • Create a subclass (in OO terms) of a WTL control (here an edit control) that does the job
  • Add the control to the project
  • Instantiate it at runtime and attach it to the control ("Windows SubClass"-it)
  • Test, debug and test

IMPORTANT: You will need to have the platform SDK installed and set an include path to the "Microsoft Platform SDK\Src\WTL\Include" directory to be able to use WTL classes and run the demo.

Task 1: Creating the subclass

Just simply creating a subclass from the WTL class CEditT (CEdit) is not what we want because that will only make it possible to change sending of Windows messages to the control from us into C++-methodcalls. That is not sufficient. We would also like to react on messages sent to the control from the Windows system. By subclassing from CWindowImpl (from atlwin.h) instead we will get a message map and that is half the job. The definition of the class could look like:
  template <class T>
  class CEditEnterAsTabT : 
       public CWindowImpl<CEditEnterAsTabT<T> ,CEdit>
  {
    public:
  BEGIN_MSG_MAP(CEditEnterAsTabT< T >)
    MESSAGE_HANDLER(WM_CHAR, OnChar)
    MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown)
  END_MSG_MAP()

  CEditEnterAsTabT(HWND hWnd = NULL){ }
  CEditEnterAsTabT< T >& operator=(HWND hWnd);
  LRESULT OnChar( UINT uMsg,
                  WPARAM wParam,
                  LPARAM lParam,
                  BOOL& bHandled);

  LRESULT OnKeyDown( UINT uMsg,
                     WPARAM wParam,
                     LPARAM lParam,
                     BOOL& bHandled)
  BOOL AttachToDlgItem(HWND parent, UINT dlgID)
   } // end class

The hard part is to get the right parameters to the CWindowImpl template. The first is the class itself (here CEditEnterAsTabT) and the seconds is a CWindow look-alike-class (here CEdit which is the same as CEditT<CWindow>) As seen the message map contains two messages. The messages we do not handle might be handled by the superclasses of this control. We add message handlers for these as we would have done in any MFC program except that we have to do the message cracking our-selves.

This class definition gives the following inheritance graph:

As seen the new class CEditEnterAsTabT is both a CEdit class, a CWindow class, and a CMessageMap class.

In this example we need to implement handlers for both the WM_KEY and WM_CHAR. Actually the WM_KEY should be enough, but if we don't implement the handler for WM_CHAR and ignore the carriage return we will get a beep. The code looks like:

LRESULT OnChar( UINT uMsg,
                WPARAM wParam,
                LPARAM lParam,
                BOOL& bHandled)
{
  //ignore without a beep
  switch (wParam)
  {
  case '\r': //Carriage return
    return 0;
    break;
  }
  return DefWindowProc(uMsg, wParam, lParam);
}

LRESULT OnKeyDown( UINT uMsg, 
                   WPARAM wParam,
                   LPARAM lParam,
                   BOOL& bHandled)
{
  switch (wParam)
  {
  case VK_RETURN:
  case VK_TAB:
    ::PostMessage (m_parent, WM_NEXTDLGCTL, 0, 0L);
    return FALSE;
  }
  return DefWindowProc(uMsg, wParam, lParam);
}
As seen the control will call the DefWindowProc if it doesn't want to catch the key or character.

Task 2: Adding the control

Include the new class in the project and make as many instances as wanted in the composite control. I suggest they are private members of the composite control class, like:
private:
  WTL::CEditEnterAsTab m_editEnterAsTab1;
  WTL::CEditEnterAsTab m_editEnterAsTab2;

Task 3: Instantiate and Attaching

The dynamic attachment of the windows control to our class is usually done in the handler for WM_INITDIALOG, i.e. OnInitDialog. This handler can be made by rightclicking on the composite control class, choosing "Add Windows Message Handler", and then choosing WM_INITDIALOG. When we want to use our message map before the controls own message handler we need to attach our own windows-procedure. We don't need to implement procedure by ourselves. It is already done. We only need to attach it. This is done by calling the membermethod SubclassWindow. I have created a method (AttachToDlgItem) that will do this for us from the dialog item ID. It looks like:
BOOL AttachToDlgItem(HWND parent, UINT dlgID)
{
  m_dlgItem = dlgID;
  m_parent = parent;
  m_hWnd = ::GetDlgItem(parent,dlgID);
  return SubclassWindow(m_hWnd);
}
The OnInitDialog handler could now look like:
LRESULT OnInitDialog( UINT uMsg,
                      WPARAM wParam,
                      LPARAM lParam,
                      BOOL& bHandled)
{
  // TODO : Add Code for message handler.
  // Call DefWindowProc if necessary.
  m_editEnterAsTab1.AttachToDlgItem(this->m_hWnd,IDC_EDITSUBCLASSED1);
  m_editEnterAsTab2.AttachToDlgItem(this->m_hWnd,IDC_EDITSUBCLASSED2);
  return 0;
}
Your new edit controls message map will now be executed before the windows control gets its chance.

Task 4: Test, Debug, and Test

You can now insert breakpoints in you code and see what happens. To test the demo project you must compile the demo project, use the ActiveX Control Test Container and insert the SubClassEditCtrlContainer ActiveX control. The behaviour should be that the [ENTER] key works as a [TAB] key for the first and third Edit-control.

Downloads

Download demo project - 18 Kb


Comments

  • Sub classing ..

    Posted by Legacy on 11/06/2002 12:00am

    Originally posted by: Jarno

    If I sub class edit box as done in this example, I still am not able to get those VK_TAB notifications with WM_KEYDOWN message ? how could I handle also TAB notification in some message handler ? Windows automatically processes that message and changes active control ..

    Reply
  • Slight mod for this code...to prevent assert with WTL7

    Posted by Legacy on 04/05/2002 12:00am

    Originally posted by: tms

    I modified this clause--my WTL7 choked on setting the m_hWnd member before calling Subclass window.


    BOOL AttachToDlgItem(HWND parent, UINT dlgID)
    {
    m_dlgItem = dlgID;
    m_parent = parent;
    return SubclassWindow(::GetDlgItem(parent,dlgID));
    }

    From the looks of the code- it's equivalent--it just doesn't assert anymore.


    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds