Jumat, 08 Mei 2009

Introduction

Subclassing is a technique that allows an application to intercept and process messages sent or posted to a particular window before the window has a chance to process them. This is typically done by replacing the Window Procedure for a window with application-defined window procedure. I will devide this article into 3:

  1. Subclassing in SDK programs,
  2. Subclassing in MFC programs,
  3. Reflected messages

Why subclass?

Sometimes you want to take the functionality of a control and modify it slightly. One example is replacing the menu in an edit control, Another is adding a context menu when you press on a button. One of the most common questions I encounter is "How do I screen out characters from an edit control?". I will show the solution to this from an MFC approach and from an SDK approach, while I try to explain Subclassing.

The need for subclassing comes from the fact that the code for the Windows controls is within Windows, meaning you cannot edit the code. Although you cannot edit the code of the control itself, you intercept the messages sent to it, and handle them your self. You do so by subclassing the control. Subclassing involves replacing the Message Handlers of the control, and passing any unprocessed message to the controls Message Handler.

SDK Subclassing

Although the Message Procedure for the control is located within windows, you can retrieve a pointer to it by using the GetWindowLong function with the GWL_WNDPROC identifier. Likewise, you can call SetWindowLong and specify a new Window Procedure for the control. This process is called Subclassing, and allows you to hook into a window/control and intercept any message it gets. Subclassing is the Windows term for replacing the Window Procedure of a window with a different Window Procedure and calling the old Window Procedure for default (superclass) functionality. Remember DefWindowProc()? instead of calling DefWindowProc for default Message Handling you use the old Window Procedure as the default Message Handler.

Implementing SDK Subclassing

So, lets try to solve the classic question "How do I screen out characters from an edit control?", or "How do I create a letter-only edit control?"

First lets analyze how an edit control works:

An edit control is a window. It's window procedure lies within windows. Among other things, whenever it gets a WM_CHAR message it adds the character to the text it contains. Now that we know that, we can simply subclass the edit control, and intercept the WM_CHAR messages. Whenever the WM_CHAR message is a letter or a key like space bar or backspace we'll pass the message to the edit control. If it isn't one of the above, we'll just "Swallow" the message, blocking it from reaching the Edit Control.

The first step to subclassing is to add a global/static WNDPROC variable that will store the address of the edit control's Window Procedure.

Collapse
WNDPROC g_OldEdit;

The second step is to create a new Window Procedure for the edit control:

Collapse
LRESULT CALLBACK NewEditProc (HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{

TCHAR chCharCode;
switch (message)
{
case WM_CHAR:
chCharCode = (TCHAR) wParam;
if(chCharCode > 0x20 && !IsCharAlpha(chCharCode))
return 0;
break;
}
return CallWindowProc (g_OldEdit, hwnd, message, wParam, lParam);
}

The IsCharAlpha function determines whether a character is an alphabetic character. This determination is based on the semantics of the language selected by the user during setup or by using Control Panel.

More interesting is the CallWindowProc function. The CallWindowProc function passes message information to the specified Window Procedure. A call to CallWindowProc will allow you to call the old Window Procedure with any message you receive, thus providing default message handling

The third step is to replace the Window Procedure for the edit control, and to store the old one in g_OldEdit. For example, if you want to subclass an edit control that resides in a dialog (hDlg) and has the ID of IDC_EDIT1 you would use the following code:

Collapse
hwnd hWndEdit = GetDlgItem(hDlg, IDC_EDIT1);
//Replace the Window Procedure and Store the Old Window Procedure

g_OldEdit = (WNDPROC)SetWindowLong(hWndEdit, GWL_WNDPROC, (LONG)NewEditProc);

The control is now subclassed. Any message to the edit control will first go through the NewEditProc Window Procedure, which will decide if the message should go to the edit control's Window Procedure .

MFC Subclassing

Subclassing in both MFC and SDK programs is done by replacing the message handlers of a control. It is rather easy to subclass in a MFC program. First you inherit your class from a class that encapsulates the functionality of a the control. In ClassWizard, click on "Add Class", then "New". For the base class, choose the MFC Control class you are deriving from, in our case, CEdit.

Using MFC relieves you from having to call the old Message Handlers, since MFC will take care of it for you.
The second step is to add Message Handlers to your new class. If you handle a message and you want the control's message handler to get a shot at it, you should call the base class member function the corresponds with the message. this is the subclassed WM_CHAR handler:

Collapse
void CLetterEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if(nChar <= 0x20 || IsCharAlpha((TCHAR)nChar))
{
//Call base class member, which will call
//the control's message handler

CEdit::OnChar(nChar, nRepCnt, nFlags);
//If not, the message is blocked
}

}

The third and final stage is to associate the window with an instance of our new class. In a dialog this is done simply by using ClassWizard to create a control member variable of your class in the window's parent, and associate it with the control.

In a non-dialog parent you should add a member variable instance of your control to it, and call one of the two CWnd Subclassing functions: CWnd::SubclassWindow or CWnd::SubclassDlgItem. Both routines attach a CWnd object to an existing Windows HWND. SubclassWindow takes the HWND directly, and SubclassDlgItem is a helper that takes a control ID and the parent window (usually a dialog). SubclassDlgItem is designed for attaching C++ objects to dialog controls created from a dialog template.

Further Reading

For a more in depth treatment of MFC subclassing see Chris Maunder's article: "Create your own controls - the art of subclassing".

Reflected Messages - MFC 4.0+

When you subclass a control, besides handling the message it receives, in MFC you can also handle the notifications it sends to it's parent window. This technique is called Message Reflecting. Windows controls often send notifications to their parents, for example, a Button will send a WM_COMMAND message telling it's parent it has been clicked. Usually it is the parent's job to handle these messages, but MFC will also allow you to handle them in the control itself. In ClassWizard these messages appear with an "=" sign before them, indicating they are Reflected Messages. You handle them just like any other message. The macros for these messages are similar to the regular messages, but have _REFLECT added to the end of the macro. For example, ON_NOTIFY() becomes ON_NOTIFY_REFLECT().

Further Reading

For a more in depth treatment of MFC reflected messages see the MFC technical notes:

  • TN014: Custom Controls
  • TN062: Message Reflection for Windows Controls

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Tidak ada komentar:

Posting Komentar

Thank you for visiting my blogg click here to return to face Layout Manager For DialogBars