The dangers of playing focus games when handling a WM_KILLFOCUS message

Date:August 8, 2005 / year-entry #216
Tags:code
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20050808-16/?p=34673
Comments:    8
Summary:I had noted last year that WM_KILLFOCUS is the wrong time to do field validation. Here's another example of how messing with the focus during a WM_KILLFOCUS message can create confusion. Consider an edit control that displays feedback via a balloon tip. For example, password edit controls often warn you if you're typing your password...

I had noted last year that WM_KILLFOCUS is the wrong time to do field validation. Here's another example of how messing with the focus during a WM_KILLFOCUS message can create confusion.

Consider an edit control that displays feedback via a balloon tip. For example, password edit controls often warn you if you're typing your password while CapsLock is in effect. One of the things you probably want to do is to remove the balloon tip if the user moves focus to another control, since there's no point telling the user about a problem with something they aren't using. You might be tempted to subclass the edit control and do something like this:

LRESULT CALLBACK EditSubclass(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch (uMsg) {
  ...
  case WM_KILLFOCUS:
    if (hwndBalloonTip) {
      DestroyWindow(hwndBalloonTip);
      hwndBalloonTip = NULL;
    }
    break;
  ...
  }
  return CallWindowProc(prevWndProc, hwnd, uMsg, wParam, lParam);
}

When you give this code a shot, it works great... unless the user clicks on the balloon tip itself the edit control's caret (the blinking insertion point thingie) disappears. What happened?

What happened is that you gummed up the focus change process by destroying the window that focus was going to! The focus change process goes like this:

  • Put focus on new focus window.
  • Send WM_KILLFOCUS to old focus window (if any).
  • Send WM_SETFOCUS to new focus window (if any).

But in the second step, we destroyed the new focus window. When the focus window is destroyed, the window manager tries to find a new focus window, and it settles upon the edit control itself. This starts a recursive focus change cycle, telling the edit control that it now has focus again.

Let's look at the flow in this nested focus change scenario when the user clicks on the tooltip window.

  • Put focus on tooltip.
  • Send WM_KILLFOCUS to edit control.
    • EditSubclass destroys the tooltip.
      • Window manager puts focus on the edit control.
      • Nobody to send WM_KILLFOCUS to.
      • Send WM_SETFOCUS to edit control.
        • EditSubclass passes WM_SETFOCUS to the original window procedure.
    • EditSubclass passes WM_KILLFOCUS to the original window procedure.
  • Send WM_SETFOCUS to tooltip - fails (tooltip was destroyed).

Do you see the problem yet?

Look at the message traffic as it reaches the original edit control window procedure:

  • WM_SETFOCUS (from the nested focus change)
  • WM_KILLFOCUS (from the original focus change)

As far as the edit control is concerned, it gained focus then lost it. Therefore, no caret, since the edit control displays a caret only when it has focus, and your recursive focus changing has resulted in the edit control thinking it doesn't have focus even though it does.

There are many ways out of this mess.

First, notice that you don't need to subclass the edit control; you can just react to the EN_KILLFOCUS notification. Second, you can respond to the EN_KILLFOCUS by posting yourself a message and destroying the tooltip on receipt of that posted message. By doing it via a posted message, you avoid the recursive focus change since your work is now being done outside a focus change cycle.


Comments (8)
  1. hey says:

    But its OK to using WM_KILLFOCUS if you aren’t creating any windows, right?

  2. Please read the linked article "WM_KILLFOCUS is the wrong time to do validation".

  3. waleri says:

    Basicaly, the problem is that EditSubclass first destroys tooltip, then pass WM_KILLFOCUS to original edit proc. Should it first send WM_KILLFOCUS to the edit window, things will work fine

  4. TC says:

    It amazes me that these things still cause problems at this stage of win32 development.

    Surely there’s a big list somewhere, that says: "If you want to achieve result ‘A’, write code ‘X’; if you want to achieve result ‘B’, write code ‘Y’", and so on?

    I don’t do win32 UI directly using the APIs, so I can’t answer that question. But if I started to do it, I’d sure be looking for that list! Why try re-inventing the wheel?

    TC

  5. carlso says:

    There’s no need to create a user-defined message in order to do a PostMessage so that you can destroy the window outside of the focus processing cycle. Just use WM_CLOSE.

    For example:

    case WM_KILLFOCUS:

    if (hwndBalloonTip) {

    PostMessage(hwndBalloonTip, WM_CLOSE, 0, 0);

    hwndBalloonTip = NULL;

    }

    break;

  6. Bryan says:

    If the tooltip balloon window is owned by a different thread, wouldn’t you have a race between the posted message being handled and your window procedure finishing? If the posted message wins, you might have the same problem.

    That may not even be an issue, though.

  7. It gums up the activation system.

  8. Koro says:

    What is the window you destroy in WM_KILLFOCUS handles WM_MOUSEACTIVATE by returning MA_NOACTIVATE?

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