Processing Keyboard Messages

WEBINAR: On-demand webcast

How to Boost Database Development Productivity on Linux, Docker, and Kubernetes with Microsoft SQL Server 2017 REGISTER >

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



Comments

  • good

    Posted by Nizvoo on 03/24/2004 10:11pm

    thank u

    Reply
  • thanks a lot

    Posted by Legacy on 01/28/2004 12:00am

    Originally posted by: nishant desai

    thank you very much for providing deep information regarding handling keyboard messages in dialogs.
    
    again thank you.

    Reply
  • Thank you!

    Posted by Legacy on 10/03/2003 12:00am

    Originally posted by: Yegor A. Blackheel

    Thank you very much, this article is very useful, particulary the covered "Accelerators" topic.

    Reply
  • Beginner question: CDialog::OnOK() ; <--- how does it recognize MyDialog

    Posted by Legacy on 04/26/2003 12:00am

    Originally posted by: Marc Noon

    Ok this is a silly point, but its something that doesn't make sense. Once I hit <enter>, or single click with the mouse while on the "OK" button it calls my OnButtonOk() and initializes some registry code. My question when CDialog::OnOK(); is called(located at the bottom of this code), how does it know that NewProjDialog is active and decides to destroy this window? Because when CDialog::OnOK() is called I'm not passing any window handles to it from CNewProjDialog() to let it know that I'm no longer interested in leaving this dialog open, and I'm now ready to close it up.

    Thanks,
    Marc

    void NewProjDialog::OnButtonOk()
    {
    // TODO: Add your control notification handler code here
    // TODO: Add extra validation here


    CWinReg b;
    CString dud;

    b.CreateSimKey("HKEY_LOCAL_MACHINE","Software\\Sim\\Sim", "Directory" ,path); // path should stay clean
    b.CreateSimKey("HKEY_LOCAL_MACHINE","Software\\Sim\\Sim", "Project", m_projname); // Add as many qualifiers as I want now
    //////////////////////////////////////////LOOK///////////////////////////////////////////


    // MessageBox(path);

    // dud.Format("%s%s",path,"\\");
    // path.Format("%s",dud);
    if (m_projname.GetLength()==0)
    {
    MessageBox("No project name selected");
    }
    else
    {
    if(chdir(path)==0)
    if(chdir(m_projname)==0)
    {
    dud.Format("path already exists");
    // MessageBox(dud);
    }
    else
    {
    if(!mkdir(m_projname)==0)
    // MessageBox("Success");
    // else
    MessageBox("Failed to make new directory");
    }
    else
    {
    dud.Format("path=%s Could not chdir path in NewProjDialog::OnOK()",path);


    // MessageBox(dud);
    }
    }


    CDialog::OnOK();


    }

    Reply
  • Going back between a combobox and and add button

    Posted by Legacy on 04/23/2003 12:00am

    Originally posted by: Marc Noon

    Hi,

    I found your code to be very rewarding. Thanks alot! However, I do have a problem with my code implementing some of your handsomely crafted solutions. The following is my attempt to explain the problem:

    I'm getting very close to going back between a combobox control and an button control in a dialog box. The problem arises when I type something into the combobox and select my OnTab function (which I created using your spiffy accelerator idea -Thx). What happens next is I call the on tab and perform the following code which you also provided.

    CWnd* pWndNext = GetNextDlgTabItem(GetFocus());
    pWndNext->CheckDlgButton(IDC_BUTTON_ADD,
    BST_INDETERMINATE );

    if (pWndNext)
    GotoDlgCtrl(pWndNext);

    pWndNext->SetFocus(); // <------ Herein lies the prob!

    When I set focus after typing the tab the first time it works like a champ. But then my m_add control once it is selected by the <return> key, set focuses back to the m_combobox. Then i type an entry the second, third or infinite time and when I hit tab key what happens is that it executes the above code again in my OnTab function, but the button is not selected, or not selected ***dark***. Its got light a light selection around it instead of a dark selection. How do I get it to be selected, or highlighted dark so that when I hit the enter key it will work a second time to infinity or until I run out of memory which ever comes first.

    The reason i want this functionality is because I want to type several things into the combobox hit tab go to my add button hit add, setfocus back to my combobox, type another field, hit tab, and enter my OnAddButton and continue. I don't want to use the mouse b/c it is to cumbersome for large data entry projects. So the main reason is for pure typing frenzied data entrying speed.

    In help in this silly problem would be greatly appreciated.


    Note: I disabled OnOK() by removing the CDialog::OnOK();, and reinserted this in my added ok button.

    Late addition: Ok i just figured out that if I select the dialog preference "Default" that it way stay highlighted black. This does not let the button appear to be selected. It just stays highlighted and when I hit <return> key it doesn't look like it gets pushed down. Maybe all these dialog boxes if selected need to be overridden as far as their on down key and on up key messages are concerned??? Anyone have a solution to this new conundrum? All I have to work with is the member control m_combo.

    Reply
  • Non-MFC dialog

    Posted by Legacy on 01/22/2003 12:00am

    Originally posted by: Alex F

    Hi,
    I am "one of few MFC programmers who prefer using PreTranslateMessage to process keys for a dialog or for controls in a dialog". However, this doesn't work in non-MFC applications. How can I disable Enter and Escape for dialog box without MFC ?

    Thank you.

    Reply
  • How to disable Ctrl-Break in dialog-based MFC programs?

    Posted by Legacy on 01/19/2003 12:00am

    Originally posted by: Truc

    How to disable Ctrl-Break in dialog-based MFC programs?

    Reply
  • Ok

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

    Originally posted by: Cristian

    I prefer PreTranslateMessage.
    This way seems more confortable (for me) to deal with dialogs/forms. Maybe it's not the best solution.
    I've replaced the Enter berhavior: it act as Enter and/or mimics Tab (moves to next control, trap the last enabled control on form); arrows can be used to navigate between controls and/or as selection key in combos/radios. My goal was to make forms behaves "MS-Dos fashion"; no mouse required, but of course not excluded. Seem's silly, but there are still tipyst for whom the mouse is a mennace (in terms of speed). Hope my English... it's readable :))

    Reply
  • Processing Keyboard Messages

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

    Originally posted by: mark

    complete, informative, and excellent work.

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

Top White Papers and Webcasts

  • As all sorts of data becomes available for storage, analysis and retrieval - so called 'Big Data' - there are potentially huge benefits, but equally huge challenges...
  • The agile organization needs knowledge to act on, quickly and effectively. Though many organizations are clamouring for "Big Data", not nearly as many know what to do with it...
  • Cloud-based integration solutions can be confusing. Adding to the confusion are the multiple ways IT departments can deliver such integration...

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date