Dialog boxes return focus to the control that had focus when you last switched away; how do I get in on that action for my own windows?


When you switch away from a dialog box, and then switch back to the dialog box, focus returns to the control that had focus when you last left the dialog box. But if you have a window that manually hosts two controls, then when the user switches away from your window, and then switches back, focus goes to, um…

Let’s find out what happens! Take our scratch program and make these changes.

HWND g_hwndChild2;

void
OnSize(HWND hwnd, UINT state, int cx, int cy)
{
 // if (g_hwndChild) {
 //    MoveWindow(g_hwndChild, 0, 0, cx, cy, TRUE);
 // }
}

BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
 g_hwndChild = CreateWindow(TEXT("button"), TEXT("&1"),
                            WS_CHILD | WS_VISIBLE |
                            WS_TABSTOP | BS_PUSHBUTTON,
                            0, 0, 100, 100,
                            hwnd, nullptr, g_hinst, 0);
 g_hwndChild2 = CreateWindow(TEXT("button"), TEXT("&2"),
                            WS_CHILD | WS_VISIBLE |
                            WS_TABSTOP | BS_PUSHBUTTON,
                            100, 0, 100, 100,
                            hwnd, nullptr, g_hinst, 0);
 return TRUE;
}

// message loop

 while (GetMessage(&msg, NULL, 0, 0)) {
  if (IsDialogMessage(hwnd, &msg)) continue;
  TranslateMessage(&msg);
  DispatchMessage(&msg);
 }

Run the program, and the first thing you notice is that neither button has keyboard focus. Focus is on the frame window, not that that helps you much, since the frame window ignores keyboard input.

Well, anyway, hit the Tab key to put focus on one of the buttons, then switch away to some other application, then switch back via Alt+Tab or clicking on the taskbar button. (Just don’t click on the window itself, because that would interfere with the experiment, since the click also sets focus to the window you clicked on.)

Again, focus is not on either button because it’s on the frame window.

Focus is on the frame window because nobody bothered to put it anywhere else. Let’s fix that. Let’s say that focus goes to the first child button when the user activates the main frame window.

void OnActivate(HWND hwnd, UINT state,
                HWND hwndActDeact, BOOL fMinimized)
{
 if (state != WA_INACTIVE && !fMinimized) {
  SetFocus(g_hwndChild);
 }
}

 HANDLE_MSG(hwnd, WM_ACTIVATE, OnActivate);

There’s a little gotcha here: We don’t want to do this when minimized. When the window is activated while minimized, the user can’t see any of the child windows, so putting focus on the child causes the user’s keypresses to start doing invisible things. (If the user hits the space bar, it will push the invisible button!) Instead, focus should stay on the frame window. It is not well-known, but if you hit the space bar when focus is on a minimized window, it will open the system menu. Keeping focus on the frame when minimized preserves this behavior.

Okay, at least this time, focus goes somewhere when the user activates our window. Of course, it would be better if we restored focus to where it was when the user last used our window. (For one thing, it means that the default pushbutton effect is more likely to remain correct!) If we don’t know where to restore the focus to, then we fall back to using the first child window.

HWND g_hwndLastFocus;
void OnActivate(HWND hwnd, UINT state,
                HWND hwndActDeact, BOOL fMinimized)
{
 if (!fMinimized) {
  if (state == WA_INACTIVE) {
   HWND hwndFocus = GetFocus();
   if (hwndFocus && IsChild(hwnd, hwndFocus)) {
       g_hwndLastFocus = hwndFocus;
   }
  } else {
   SetFocus(g_hwndLastFocus ? g_hwndLastFocus
                            : g_hwndChild);
  }
 }
}

 HANDLE_MSG(hwnd, WM_ACTIVATE, OnActivate);

The basic idea is that when the window is deactivated, we remember the window that had focus, and when the window is reactivated, we restore the focus to that same window (or to our first child if we don’t know what the previous focus was).

Again, it’s important to watch out for the minimized window case. If the window is minimized when the user deactivates it, we would end up saving the frame window for future restoration, when in fact we should just ignore the entire interaction while minimized.

Note that even when not minimized, we do not try to save focus if it belongs to something outside our frame window. For our simple program, this is more of a safety check than something we expect to happen.

Next time, we’ll see an unexpected consequence of this auto-restore behavior.

Comments (16)
  1. alegr1 says:

    How to get rid of an annoying behavior when EDIT control automatically selects the whole text when the dialog box restores focus to it on app activation?

  2. Paul Parks says:

    @alegr1: Basically, use Raymond's code above to check if the edit control has focus, and save the selection range when the window loses focus and restore the selection range when the window regains focus.

  3. Ivo says:

    No need to remember the edit control selection. It already remembers its own selection. To avoid changing the selection on focus changes, the edit box needs to not return the flag DLGC_HASSETSEL when processing the message WM_GETDLGCODE. Of course then you are changing the behavior not only when the dialog is activated, but also when you Tab between controls. I don't know of an easy way to distinguish between these cases.

  4. Adrian says:

    While I've known that the application should keep track of the last child to have focus and to restore it, it's never been clear to me when to do this.  Your sample uses WM_ACTIVATE, which makes sense, but I've also seen it done on WM_SETFOCUS itself (when the host window gets focus, it redirects it to the appropriate child).  I'm wondering if this is effectively the same behavior, or if there's some subtlety that makes handling WM_ACTIVATE more appropriate.

  5. Joshua says:

    Where's the interaction document when you need it?

  6. Anonymous Coward says:

    It strikes me that this is the sort of standard behaviour that the window manager should do, especially since doing it yourself is tricky and easy to get wrong.

  7. Myria says:

    Wow, this is actually something in one of our programs that annoyed me today.  *Writes a fix and waits for the next one which explains the "unexpected consequence"*

  8. Neil says:

    I can't reproduce the alleged space key system menu behaviour on a minimised window, and I tried it in Windows 95, 2000, XP and 7 just to be sure.

    @Adrian WM_SETFOCUS does look as if it might be a better idea because you don't seem to get that message for minimised windows.

  9. Marc K says:

    @alegr1: To some it's not annoying.  I'm actually very annoyed when clicking on an edit control does not select the contents.  Nine times out of 10, my goal is to delete the existing text and replace it.  Those controls that don't adhere to the Windows convention make more work for me.

  10. foo says:

    @Neil. I got this to work on Windows 7 by: 1. Killing explorer.exe via task manager. 2. Closing all app windows except the one I wanted to pick on (this included Task Manager). 3. Minimising the desired Window. Without explorer there is no taskbar to take over things (my theory). If it is the only window it should have its minimised caption bar highlighted. 4. Press spacebar. The system menu opens. Caveats: 1. I run Win7 in classic mode (or whatever it's called). 2. There's probably a better way to skin the cat.

  11. WndSks says:

    @foo: Explorer and most other shell replacements apply SPI_SETMINIMIZEDMETRICS with ARW_HIDE ("Hide minimized windows by moving them off the visible area of the screen") which is probably why you have to kill explorer to get back to the progman minimized style.

  12. Marc K says:

    …or I want to copy the text to the clipboard for further use.  Either way, having the edit control select the contents on focus has been highly useful for me.

  13. @Marc K says:

    The difference between your use-case and that of alegr1 is: You are talking about focus change within the same app, or the same dialog. alegr1 is talking about switching between different apps. An app switch in itself should not alter the input or dialog state on one of the apps.

  14. Veltas says:

    Sorry, how exactly do you put focus on a minimized window? Or rather, when would the user get to this situation?, I suppose is a better question.

    No wonder "it is not well-known"!

  15. Neil says:

    @foo Not wanting to kill explorer.exe I searched for another way to create a minimised window but so far I've only been able to achieve this on one application running on Windows 95, which just beeps if I hit space on a minimised window.

  16. Zhila says:

    One way to set focus on a mimimized window is to use the Alt+Esc and Alt+Shift+Esc keys.  I remember finding out about this as a way to switch from a full screen application back in the days of Windows 95 and Windows 98.  The keyboard I was using back then didn't have the Windows key, but I knew about Ctrl+Esc.  However, one time I accidentally pressed Alt+Esc instead, and noticed that the taskbar came up front without the start menu displaying (which is what I wanted), so I became accustomed to Alt+Esc, and favored it more than Alt+Tab.  There was also an entry on this blog some time ago on exactly what the combination does, and, because it's much lesser known than the Alt+Tab combination, most utility apps which intercept the Alt+Tab don't intercept the Alt+Esc.  Alt+Esc takes the current active window, puts it on the bottom of the window stack and at the same time takes the second window on the stack and makes it active.  Alt+Shift+Esc does the exact opposite.  The only effect this keyboard shortcut has is modifying the window stack as well as making sure the window on top of the stack is active (though it may possibly be a job of the window manager to do the latter, I'm not sure).  In most cases, the stack order has all minimized windows on bottom, and all not minimized windows on top, but Alt+Esc can change this order to have minimized windows on top, and thus active.  This should allow a simple way to have a minimized window be active without having to kill explorer or attempt to start minimized or anything like that.  In fact, I just tested this with Internet Explorer 11 on Windows 7, and I in fact did see the system menu open when pressing space.  In fact, I think I may have seen this happen once before and I had wondered how and why this happens, and now I know.

Comments are closed.