Using the TAB key to navigate in non-dialogs

Date:October 21, 2003 / year-entry #104
Tags:code
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20031021-00/?p=42083
Comments:    4
Summary:The IsDialogMessage function works even if you aren't a dialog. As long as your child windows have the WS_TABSTOP and/or WS_GROUP styles, they can be navigated as if they were part of a dialog box. One caveat is that IsDialogMessage will send DM_GETDEFID and DM_SETDEFID messages to your window, which are message numbers WM_USER and...

The IsDialogMessage function works even if you aren't a dialog. As long as your child windows have the WS_TABSTOP and/or WS_GROUP styles, they can be navigated as if they were part of a dialog box. One caveat is that IsDialogMessage will send DM_GETDEFID and DM_SETDEFID messages to your window, which are message numbers WM_USER and WM_USER+1, so you should avoid using those messages in your window procedure for some other purpose.

These changes to our scratch program illustrate how you can use the TAB key to navigate within a non-dialog.

HWND g_hwndLastFocus;

void OnSetFocus(HWND hwnd, HWND hwndOldFocus)
{
    if (g_hwndLastFocus) {
        SetFocus(g_hwndLastFocus);
    }
}

void OnActivate(HWND hwnd, UINT state,
                HWND hwndActDeact, BOOL fMinimized)
{
    if (state == WA_INACTIVE) {
        g_hwndLastFocus = GetFocus();
    }
}

// Just display a messagebox so you can see something
void OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
    switch (id) {
    case 100:
        MessageBox(hwnd, TEXT("Button 1 pushed"),
                   TEXT("Title"), MB_OK);
        break;
    case 101:
        MessageBox(hwnd, TEXT("Button 2 pushed"),
                   TEXT("Title"), MB_OK);
        break;
    case IDCANCEL:
        MessageBox(hwnd, TEXT("Cancel pushed"),
                   TEXT("Title"), MB_OK);
        break;
    }
}

BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
    HWND hwndChild =
        CreateWindow(
        TEXT("button"),                 /* Class Name */
        TEXT("Button &1"),              /* Title */
        WS_CHILD | WS_VISIBLE | WS_TABSTOP |
        BS_DEFPUSHBUTTON | BS_TEXT,     /* Style */
        0, 0, 100, 100,                 /* Position and size */
        hwnd,                           /* Parent */
        (HMENU)100,                     /* Child ID */
        g_hinst,                        /* Instance */
        0);                             /* No special parameters */
    if (!hwndChild) return FALSE;
    g_hwndLastFocus = hwndChild;

    hwndChild =
        CreateWindow(
        TEXT("button"),                 /* Class Name */
        TEXT("Button &2"),              /* Title */
        WS_CHILD | WS_VISIBLE | WS_TABSTOP |
        BS_PUSHBUTTON | BS_TEXT,        /* Style */
        100, 0, 100, 100,               /* Position and size */
        hwnd,                           /* Parent */
        (HMENU)101,                     /* Child ID */
        g_hinst,                        /* Instance */
        0);                             /* No special parameters */
    if (!hwndChild) return FALSE;

    hwndChild =
        CreateWindow(
        TEXT("button"),                 /* Class Name */
        TEXT("Cancel"),                 /* Title */
        WS_CHILD | WS_VISIBLE | WS_TABSTOP |
        BS_PUSHBUTTON | BS_TEXT,        /* Style */
        200, 0, 100, 100,               /* Position and size */
        hwnd,                           /* Parent */
        (HMENU)IDCANCEL,                /* Child ID */
        g_hinst,                        /* Instance */
        0);                             /* No special parameters */
    if (!hwndChild) return FALSE;

    return TRUE;
}

//  Add to WndProc

    HANDLE_MSG(hwnd, WM_COMMAND, OnCommand);
    HANDLE_MSG(hwnd, WM_ACTIVATE, OnActivate);
    HANDLE_MSG(hwnd, WM_SETFOCUS, OnSetFocus);

    // Add blank case statements for these to ensure we don't use them
    // by mistake.
    case DM_GETDEFID: break;
    case DM_SETDEFID: break;

//  Change message loop
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        if (IsDialogMessage(hwnd, &msg)) {
            /* Already handled by dialog manager */
        } else {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

One subtlety is the additional handling of the WM_ACTIVATE and WM_SETFOCUS messages to preserve the focus when the user switches away from the window and back. Notice also that we picked Button 1 as our initial default button by setting it with the BS_DEFPUSHBUTTON style.

Observe that all the standard dialog accelerators now work. The TAB key navigates, the Alt+1 and Alt+2 keys act as accelerators for the two buttons, the Enter key presses the default button, and the ESC key pushes the Cancel button since its control ID is IDCANCEL.


Comments (4)
  1. Anonymous says:

    The second "case 101:" should probably be "case IDCANCEL:"

  2. Raymond Chen says:

    Oops. Fixed, thanks. I did have it working, honest; this was a transcription error.

  3. Anonymous says:

    I just thought that you were so good you didn’t even need to compile it!

  4. We’ll hook it up later.

Comments are closed.


*DISCLAIMER: I DO NOT OWN THIS CONTENT. If you are the owner and would like it removed, please contact me. The content herein is an archived reproduction of entries from Raymond Chen's "Old New Thing" Blog (most recent link is here). It may have slight formatting modifications for consistency and to improve readability.

WHY DID I DUPLICATE THIS CONTENT HERE? Let me first say this site has never had anything to sell and has never shown ads of any kind. I have nothing monetarily to gain by duplicating content here. Because I had made my own local copy of this content throughout the years, for ease of using tools like grep, I decided to put it online after I discovered some of the original content previously and publicly available, had disappeared approximately early to mid 2019. At the same time, I present the content in an easily accessible theme-agnostic way.

The information provided by Raymond's blog is, for all practical purposes, more authoritative on Windows Development than Microsoft's own MSDN documentation and should be considered supplemental reading to that documentation. The wealth of missing details provided by this blog that Microsoft could not or did not document about Windows over the years is vital enough, many would agree an online "backup" of these details is a necessary endeavor. Specifics include:

<-- Back to Old New Thing Archive Index