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.