Never leave focus on a disabled control


One of the big no-no's in dialog box management is disabling the control that has focus without first moving focus somewhere else. When you do this, the keyboard becomes dead to the dialog box, since disabled windows do not receive input. For users who don't have a mouse (say, because they have physical limitations that confine them to the keyboard), this kills your dialog box.

(I've seen this happen even in Microsoft software. It's very frustrating.)

Before you disable a control, check whether it has focus. If so, then move focus somewhere else before you disable it, so that the user isn't left stranded.

If you don't know which control focus should go to, you can always let the dialog manager decide. The WM_NEXTDLGCTL message once again comes to the rescue.

void DialogDisableWindow(HWND hdlg, HWND hwndControl)
{
  if (hwndControl == GetFocus()) {
    SendMessage(hdlg, WM_NEXTDLGCTL, 0, FALSE);
  }
  EnableWindow(hwndControl, FALSE);
}

(And of course you should never disable the last control on a dialog. That would leave the user completely stranded with no hope of escape!)

[This was supposed to go out yesterday but the autoblog tool had a bad day and forgot to post this. Sorry.]

Comments (16)
  1. Adrian says:

    And don’t forget to disable controls when you hide them!

  2. Shay Erlichmen says:

    Well Mr. Chen, If it’s such a no-no to disbale a focus control why to you allow it. (#define you Microsoft).

    Why doens’t it give you an error if you try to disbale a focus control?, let me take a wild guess: In Windows 2.0 some MS software counts on this behaviour.

  3. For users who don’t have a mouse (say, because they have physical limitations that confine them to the keyboard)

    … or because the mouse is not such a good input device after all, especially for filling out forms.

  4. Shay: It’s quite simple. If you’re programming win32, you’re expected to know what you’re doing. You’re expected to be a reasonably intelligent software engineer, who understands the consequences of their actions.

    Why doesn’t it give you an error? Because not all controls that can take focus and can be marked disabled are used in dialogs, and those other controls may have different behavior.

    If you want a system which mollycoddles you with defensive programming to the nth degree regardless of whether or not it makes sense for all use cases, might I suggest Visual Basic? Or perhaps Tcl/tk?

  5. Mike Weller says:

    Just last week I implemented this very same thing.

    The only other problems I’m having are because I’m using controls in a non-dialog window, with IsDialogMessage(…) in the main message loop.

    This enables dialog keyboard navigation, but doesn’t handle everything quite correctly. When the main window loses focus, and you return to it, the default button is still ‘default’ but without the keyboard focus! That and a few other niggles.

    But I can live with it – I know I should be using a dialog box instead.

  6. Puzzled exAAPL guy says:

    Simon, I really don’t understand your logic.

    What useful capability would you lose in this case if the dialog manager did something sensible instead of allowing the programmer to shoot himself in the foot?

    Could you elaborate (in a technical manner, without insulting my intelligence, please)?

    ‘Cause from my perspective, it just looks like something the Dialog Manager programmers originally forgot to implement. Why not fix it for everyone at the source?

  7. Raymond Chen says:

    Often a dialog box will want to disable one window and enable another. If it did them in that order, then the disable would fail and you would now have two enabled buttons instead of the one button that the program intended.

    And what if you really wanted to disable the last window on the dialog? Say because all of the options are unavailable?

    This could lead to serious problems, since the app might accidentally leave the "send" button enabled when it should be disabled. The user clicks the button and now something that shouldn’t have been sent got sent.

    But the upshot is that the window manager’s design is to do what you tell it, not to second-guess you. To paraphrase Doug Gwyn, "The window manager was not designed to stop you from doing stupid things because that would also stop you from doing clever things."

    For example, suppose your program’s design was to "disable everything, then selective re-enable what you want to enable." That would not be possible if the dialog manager didn’t allow you disable the last control on a dialog.

    And besides, EnableWindow operates at the window manager level, not the dialog manager. So it couldn’t prevent you from doing it even if it wanted to. (It doesn’t know that the window is on a "dialog"; all it sees is "Hey here’s a window and the program wants to disable it.")

  8. Norman Diamond says:
    1. Suppose the function (or message or whatever) that disables a control would check if the control has focus and, if so, automatically change the focus to the window that contains the control.

      For comparison, if an application is running in one window, if the user minimizes or closes the application, then focus sometimes changes to another application’s window or sometimes changes to no window at all. If the user types on the keyboard while no window has focus, the input is ignored, but nothing is dead. The user can still click any window or press alt+tab or even restore the window that the user had just minimized.

      2. An interesting pair of notes here.

      8/4/2004 12:47 PM Simon Cooke [exMSFT]:

      > You’re expected to be a reasonably

      > intelligent software engineer, who

      > understands the consequences of their

      > actions.

      and the base note:

      > (I’ve seen this happen even in Microsoft

      > software.

      Well, maybe it’s fine to expect every Win32 programmer to be more reasonably intelligent and understanding than some Microsoft software engineers who programmed in Win32.

      But what about MFC programmers? An edit box might come from CEdit, whose member function EnableWindow is inherited from CWnd. The MSDN Library for CWnd mentions that disabling a window implicitly disables child windows but doesn’t mention the parent window. It also says:

      + An application can use this function to

      + enable or disable a control in a dialog box.

      + A disabled control cannot receive the input

      + focus, nor can a user access it.

      This also doesn’t warn that all keyboard access to the dialog box dies with it. If you wish for MFC programmers to also be more reasonably intelligent and understanding than some Microsoft programmers are, then surely Mr. Chen’s warning does need to be added to the MSDN Library.

  9. Raymond Chen says:

    "Suppose the function (or message or whatever) that disables a control would check if the control has focus and, if so, automatically change the focus to the window that contains the control."

    Sure it could’ve done that but that doesn’t often help (for example, putting focus on the dialog box frame doesn’t accomplish much of anything), and sometimes it’s undesirable. E.g., in the "disable everything then re-enable what you want" scenario, this would end up moving focus to the frame even though nothing "happened".

    At any rate, it’s too late to change the behavior now.

  10. Mike says:

    What’s wrong with having a new dialog manager that has roughly the same design as the old one but is cleaned up? That way new code could be written (or ported) against saner APIs. This is the sort of versioning used on Linux with Qt and GTK+, and it works very well.

  11. asdf says:

    This is a problem with the winapi and MSDN in particular. Some of the functions have some logic to do the right thing and some of them are just low level functions that expect the user to do all the smarts. I can’t think of a function off the top of my head right now but for example lets say you’re writing a streams class and have a member called close(). The obvious thing to do would have the stream class call flush() inside the close() call (this is analagous to your argument about having the function change the focus to the next window on a call to disable). You can argue it both ways but the problem boils down to MSDN not explicitly stating what will happen in this case. So it’s up to you to experiment and that is no guarantee it will work with the ever changing future versions of Windows. Funny Shay brought up DeleteObject. I somewhat remember in 16 bit windows that if you did that call on a system object, bad things would happen unlike the modern versions of windows where it would just ignore it.

  12. Jack Mathews says:

    Simon:

    The dialog manager may be seperate from the window manager, but Windows was designed with dialogs in mind. Therefore, for the window manager to have no left the appropriate hooks for the dialog manager to handle such mundane things as this is a problem in the design.

    If you design an API where common tasks can be done wrong so easily without obvious error or incident, and the right way to do them requires everyone doing it to write their own helper function, then the system is flawed.

  13. Raymond Chen says:

    Activation does get moved around automatically and even though the window manager is trying to do "the right thing" you still have to be careful or you get undesired behavior:

    http://weblogs.asp.net/oldnewthing/archive/2004/02/27/81155.aspx

    So no matter how you design it, it’s still subtle and people will get it wrong. Historically, remember, the window manager was designed for computers with 640K of memory. It couldn’t be too fancy; there wasn’t enough memory to be fancy.

    (And giving the window manager special hooks into the dialog manager violates the principles of layered architectures. The lower level should not be aware of the upper level. If it did, then it would be impossible for people to write their own dialog manager since the window manager would talk to the "native" dialog manager instead of their custom one.)

  14. Shay Erlichmen says:

    I’ve been programming in Win32 API for ages now and I consider myself a *clever* Win32 programmer.

    The example that Mr. Chen gives is one of many behaviors that I need to take into account each time that I want to write a Win32 programmer.

    Not all Win32 programmer are cleaver and that tend to make many mistake (DeleteObject anyone?). I Guess that’s why BoundsChecker and friends are so popular. The Win32 API is dead, The only problem is that even more advance APIs like WinForms and Avalon (in the past it was MFC and WTL) have ridiculous legacy that will probably make this blog valid till the 22 century

  15. Jack Mathews says:

    Raymond:

    I didn’t say that window manager would hook directly into the dialog manager, but that it would "leave hooks" for the dialog manager. Meaning that there would be hooks that a dialog manager could use, or any other similar manage could use.

    While you can’t design a general system knowing what every client would when, when you have example clients (like dialogs) that have specific needs, you can put the functionality in so that clients (including the dialog manager) can service those needs with minimal effort.

  16. Bill Rayer says:

    "Never leave focus on a disabled control"

    I’ve spent some time implementing a dialog manager similar to the one built into Windows, as part of http://www.lingolanguage.com and have issues with this comment!

    First, if you call EnableWindow(hCtl,FALSE) for EDIT or BUTTON classes (pushbuttons) the spacebar still presses the button, and keystrokes still feed into the edit control. So it is not quite correct to say the keyboard becomes dead.

    Second, if you call EnableWindow(hWnd,FALSE) for a pseudo-dialog (eg a WS_OVERLAPPED window containing child controls) the control with the focus retains the focus.

    Finally, in some cases it’s necessary to disable a window and leave the focus on a control in a window. Eg consider a dialog with a push button to run an external program that displays a window – we want the focus to stay in the temporarily disabled dialog, and allow the external program to run in a window that is not made active. By doing this the focus returns to the original dialog when it is re-enabled.

Comments are closed.

Skip to main content