Processing Keyboard Messages

Environment: Windows and MFC

Most of the material in this article discusses the processing of keyboard messages in dialogs and MFC form views. Much of this also applies to other types of windows. Topics covered are:

  • A summary of dialogs, controls, and keyboard messages
  • The WM_GETDLGCODE Message for processing most any key typed in a control
  • Using Accelerator Keys to process most any key (combination) for an entire dialog
  • An alternative solution for disabling the Enter and/or Escape key(s)
  • Short comments about PreTranslateMessage

There are many more good solutions for processing keys and overriding how keys are processed. It is difficult to decide which other ones are worth mentioning. Therefore, the solutions described here are limited to ones documented in the MSDN (in fact, they are either in the Microsoft Knowledge Base or the Microsoft Systems Journal).

Summary of Dialogs, Controls, and Keyboard Messages

Windows programmers, especially beginners, often want to process keyboard messages for an entire dialog instead of for individual controls in the dialog. Another behavior that is often overridden is the behavior of the Enter and/or Escape keys specifically.

Windows does not provide a direct solution for processing keyboard messages for an entire dialog because that is inconsistent with the design of dialog boxes. Normally, there will always be a control in a dialog that has the focus. The only way I know of to have a dialog without any control having the focus is to have no control (enabled) in the dialog. The normal method of processing keyboard messages in a dialog is for the messages to always be processed by a control that has the focus in the dialog. Therefore, in a dialog (or form view), whenever possible, process all keyboard messages in a control. In other words, have a control, such as an edit control, in the dialog that usually has the focus and processes all keyboard messages in that control.

When a control has the focus, it receives messages for keys entered by the keyboard. The type of control and the styles set for it determine what keys are sent for us to process. For some controls, such as the edit control, there are styles that affect what keys are sent by the default processing. In most cases, it is just the Return (Enter) key that is affected. For example, for multi-line edit controls, the Enter key can be sent but only if the edit control is a multi-line edit control and if the "Want Return" style is set. Which keys are sent or are not sent by default can be overridden however by processing theWM_GETDLGCODE message, described below.

The Enter and Escape keys are a bit of an exception. Unless the Enter key is processed by a control, it will have the effect of the default (usually "OK") button for the dialog being pressed. The Escape key will generate the IDCANCEL command. Often, Windows programmers do not like that behavior. Beginner Windows programmers need to understand that this is standard behavior and therefore it is not necessary to change it. When this behavior is to be overridden, the easiest and most practical solution is to make the Enter and/or Escape key(s) accelerator key(s). There is an explanation and sample code for doing this below. Also see the topic "Dialog Box Keyboard Interface" in the "Dialog Box Programming Considerations" article of the Windows SDK documentation; the online copy is at Dialog Box Programming Considerations.

Most any key can be used as an accelerator key and therefore most any key can be overridden for an entire dialog. The disadvantage of using accelerator keys is that a multi-line edit control will not receive the Enter key unless additional code is used; however, the alternative described in "Disabling Enter and/or Escape Key(s)" below can be used instead for disabling the Enter key from closing the dialog.

The WM_GETDLGCODE Message

You can process WM_GETDLGCODE in a class derived from an MFC control class, such as CButton and CEdit. In your message handler, return DLGC_WANTMESSAGE or DLGC_WANTALLKEYS instead of, or in addition to, the return value of the base class OnGetDlgCode. Then, your keyboard message handlers will receive messages for more of the keys than it would by default. The following are two possible samples:

UINT CButtonx::OnGetDlgCode() {return 
     CButton::OnGetDlgCode()| DLGC_WANTMESSAGE;
}
UINT CButtonx::OnGetDlgCode() {
return DLGC_WANTALLKEYS;
}

Then, if you process the WM_CHAR message, your OnChar function will get more keys than if you did not override OnGetDlgCode. The Enter ("Return") key is one that will be received that might not be received without the OnGetDlgCode override. When the Enter ("Return") key is pressed, the nChar parameter of OnChar will be '\r' and for the WM_KEYDOWN and WM_KEYUP messages, nChar will be VK_RETURN. The NM_RETURN notification message also might not be sent without the WM_GETDLGCODE message being processed to request that the Enter key be sent.

Using Accelerator Keys

Dialogs do not process accelerator tables, but support of them can easily be added. The following describes how to revise your MFC CDialog-derived class to process an accelerator table for a dialog. Code must be added to CDialog::OnInitDialog to load the accelerator table and CDialog::PreTranslateMessage must be overridden to process accelerator keys.

If OnInitDialog for the dialog has not yet been overridden, use ClassWizard to add an override for it. Then, after the call to the base class OnInitDialog, add:

m_hAccel = ::LoadAccelerators(AfxGetResourceHandle(),
             m_lpszTemplateName);
if (!m_hAccel)
  MessageBox("The accelerator table was not loaded");

Note that m_lpszTemplateName is a protected and therefore undocumented member of CDialog and CFormView, but it can be used in classes derived from them. It points to the name or MAKEINTRESOURCE value for the dialog. That value can be used as shown if you copy the dialog's id from its properties to the id for the accelerator table. Alternatively, you can use something such as:

m_hAccel = ::LoadAccelerators(AfxGetResourceHandle(),
             MAKEINTRESOURCE(IDR_ACCELERATOR1);

Then, in the dialog's header, add:

HACCEL m_hAccel; // accelerator table

Then, use ClassWizard to add an override for PreTranslateMessage. Add the following to it:

if (WM_KEYFIRST <= pMsg->message && pMsg->message
                <= WM_KEYLAST)
  if (m_hAccel && ::TranslateAccelerator
     (m_hWnd, m_hAccel, pMsg))
    return TRUE;

You now have a dialog class that processes accelerator keys.

The next thing to do is to create an accelerator table resource. The resource id of the accelerator table resource must be the same id specified in the LoadAccelerators function; if you use the m_lpszTemplateName member variable, the accelerator table resource id must also be the same as the dialog resource id.

Entries in the accelerator table specify a key (combination) and a corresponding command id.

After creating the accelerator table, use ClassWizard to add a handler for the id(s) you provided in the accelerator table. Then, you can do most anything in the handler that you need to do when the key is typed. If you do nothing in the handler, nothing happens when that key (combination) is typed. Assuming that it is the Enter key that was entered into the table, pressing the Enter key would do nothing.

The following code can be used to simulate the tab key. This is most often used in a handler for the Enter key.

CWnd* pWndNext = GetNextDlgTabItem(GetFocus());
if (pWndNext)
  GotoDlgCtrl(pWndNext);

In C++ Q&A: Enabling Menus in MFC Apps, Changing the Behavior of Enter with DLGKEYS Sample App (the second question), the sample uses "pWndNext->SetFocus();" instead of "GotoDlgCtrl(pWndNext);" but in the second portion of the MSJ C++ Q&A for July 1997 article, Paul DiLascia explains why it is better to use GotoDlgCtrl.

Disabling the Enter and/or Escape Key(s)

The Enter and/or Escape Key(s) can be disabled by making the resource id for the Okay and/or Cancel buttons, not IDOK or IDCANCEL (correspondingly). See the Microsoft Knowledge Base Article Q122489 ("How to Disable Default Pushbutton Handling for MFC Dialog") for the KB article describing this solution. The advantage of this solution for disabling the Enter key from closing the dialog compared to using an accelerator key is that a multi-line edit control will continue to work as it should without requiring other modification.

The following is the essential part of it. I am assuming that a handler for the Okay and/or Cancel buttons has not yet been created. If it has (they have), you can adjust the following correspondingly.

  • Use the ClassWizard to create a handler for the Okay and/or Cancel button. When creating the handler, the object will be IDOK (for disabling the Enter key) or IDCANCEL (for disabling the Escape key). Choose the BN_CLICKED message.
  • Then, use the resource editor to modify the dialog by changing the resource id for the Okay and/or Cancel button(s) to something else. For the Okay button, uncheck the checkbox for the Default Button property.
  • Use ClassWizard again to create a handler for the button(s) with the new resource id. That is, the object will be the new resource id assigned to the button.
  • Finally, move the call to the base class function (CDialog::OnOk or CDialog::OnCancel) from the OnOk or OnCancel function to the new handler function. The name of the new handler function(s) depends on the resource id you gave to the Okay and/or Cancel button.

Notice that if the focus is on either of the buttons modified as above, pressing Enter will send the command (the button will work). If the focus is not on either of the buttons modified, entering the corresponding key (Enter and/or Escape) will not cause the command to be sent; in other words, the key is disabled.

If the Escape key is disabled in this manner, the Close system command (including the "X" at the top right) does not work. This can be fixed simply by using the ClassWizard again to create a handler for the WM_CLOSE message. In the generated handler, change:

CDialog::OnClose();

to:

CDialog::OnCancel();.

PreTranslateMessage

There are a few MFC programmers who prefer using PreTranslateMessage to process keys for a dialog or for controls in a dialog. PreTranslateMessage is an MFC function that processes accelerator keys (as in the sample above) and tooltips but is not intended to process keyboard messages for the other purposes the above solutions do. By using the WM_GETDLGCODE message solution, it is possible to use Windows and MFC to write functions that are specific to each control and that is for a specific message sent to the control. By using the accelerator table solution, it is possible to write a function specific to each key (combination) in the accelerator table, whereas the PreTranslateMessage function processes all messages for all controls. It can of course dispatch combinations of messages and controls to other functions, but why not use MFC as shown above to do that?

References

The following articles are directly relevant to the solutions provided above.

The following articles provide other possible solutions:

Downloads

None