How to set focus in a dialog box

Date:August 2, 2004 / year-entry #296
Tags:code
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20040802-00/?p=38283
Comments:    22
Summary:Setting focus in a dialog box is more than just calling SetFocus. A dialog box maintains the concept of a "default button" (which is always a pushbutton). The default button is typically drawn with a distinctive look (a heavy outline or a different color) and indicates what action the dialog box will take when you...

Setting focus in a dialog box is more than just calling SetFocus.

A dialog box maintains the concept of a "default button" (which is always a pushbutton). The default button is typically drawn with a distinctive look (a heavy outline or a different color) and indicates what action the dialog box will take when you hit Enter. Note that this is not the same as the control that has the focus.

For example, open the Run dialog from the Start menu. Observe that the OK button is the default button; it has a different look from the other buttons. But focus is on the edit control. Your typing goes to the edit control, until you hit Enter; the Enter activates the default button, which is OK.

As you tab through the dialog, observe what happens to the default button. When the dialog box moves focus to a pushbutton, that pushbutton becomes the new default button. But when the dialog box moves focus to something that isn't a pushbutton at all, the OK button resumes its position as the default button.

The dialog manager remebers which control was the default button when the dialog was initially created, and when it moves focus to something that isn't a button, it restores that original button as the default button.

You can ask a dialog box what the default button is by sending it the DM_GETDEFID message; similarly, you can change it with the DM_SETDEFID message.

(Notice that the return value of the DM_GETDEFID message packs the control ID in the low word and flags in the high word. Another place where expanding dialog control IDs to 32-bit values doesn't buy you anything.)

As the remarks to the DM_SETDEFID function note, messing directly with the default ID carelessly can lead to odd cases like a dialog box with two default buttons. Fortunately, you rarely need to change the default ID for a dialog.

A bigger problem is using SetFocus to shove focus around a dialog. If you do this, you are going directly to the window manager, bypassing the dialog manager. This means that you can create "impossible" situations like having focus on a pushbutton without that button being the default!

To avoid this problem, don't use SetFocus to change focus on a dialog. Instead, use the WM_NEXTDLGCTL message.

void SetDialogFocus(HWND hdlg, HWND hwndControl)
{
 SendMessage(hdlg, WM_NEXTDLGCTL, (WPARAM)hwndControl, TRUE);
}

As the remarks for the WM_NEXTDLGCTL message observe, the DefDlgProc function handles the WM_NEXTDLGCTL message by updating all the internal dialog manager bookkeeping, deciding which button should be default, all that good stuff.

Now you can update dialog boxes like the professionals, avoiding oddities like having no default button, or worse, multiple default buttons!


Comments (22)
  1. Serge Wautier says:

    I was sure I knew everything about dialogs. Now I learn that not only was I wrong but I’m not even a professional ! Please don’t tell the guy who signs my pay check at the end of each month ;-)

    Also, MFC seems to make the same mistake. Even though there is a CWnd::GotoDlgCtrl(), MFC doesn’t uses it much itself (e.g. see CDataVaildation::Fail())

  2. Matthias says:

    Hi,

    maybe this is off-topic, but I think I found a bug in the dialog manager.

    I created a standard dialog in Visual C++ 6 and tested it there. (I didn’t wrote any code).

    I could produre this behaviour:

    Abbrechen (Cancel) is the default push button, but OK has the focus:

    http://matthias-hoffrichter.privat.t-online.de/dialog1.jpg

    And if you now activate the OK button. You have 2 default push buttons.

    http://matthias-hoffrichter.privat.t-online.de/dialog2.jpg

    I hope you can reprodure this:

    Press the tab-key and hold it down. The focus rotates between the 2 buttons. Now release the tab-key and press a button.

    Maybe this explanation is not correct because this happens all very fast. ;)

    Play around with tab and click the button.

  3. Matthias says:

    Ah, one correction: I think it requires a double click on the button.

  4. …set focus in a dialog box. I always thought MacOS Carbon was messy….

  5. Matthias says:

    Okay, when I subclass the buttons, catch WM_LBUTTONDBLCLK and just return 0 this behaviour doesn’t occur.

    I uploaded a small example if you want to test it.

    http://matthias-hoffrichter.privat.t-online.de/Dialog.zip

    Does anybody understand whats going on?

  6. Ben Cooke says:

    By the "rules" of dialogs, if a button has focus then it must be the default button. However, when your dialog is initializing the first control to get focus is a button. The dialog manager doesn’t seem to have noticed that it has to change the default button in this case, which might well be causing the rest of the confusion.

    Sadly, I’m not using Windows right now so I can’t play with your code.

  7. Norman Diamond says:

    Sometimes it sure would be better to have no default button. An example is the Device Manager snap-in of MMS. If the user has ever used the tab key to move from one control to another, then the user probably expects the enter key to do invoke the active control rather than invoke the OK button. There used to be some kinds of dialogs (not in MMS) where, for example, if a combo box had focus then the enter key would make the list drop down, the arrow keys would move the selection to a lower or higher list entry, and then the enter key would act on the selected entry. In Device Manager, sometimes the user has to press the enter key, sometimes the user has to just press arrow keys (and not see the entire list but memorize all the entries as they get displayed one at a time), sometimes the user has to press the space key, etc. If the user guesses wrong at any time and hits the enter key when something else was necessary, then the OK button automatically takes over, commits some undesired change, closes the dialog, and makes the user hunt for all the operations in sequence before finding the same point in the dialog again.

    Well, if the user is trying to debug something other than the mouse then the problem isn’t usually that bad. But nonetheless it is bad when it doesn’t have to be. If the user has tabbed off the default button, then the new default should either be the focused control (if it is meaningful to be a default) or should be none at all, i.e. a no-op, instead of ambushing and reverting to a button that the user has tabbed off from.

  8. Mike Dunn says:

    Norman> Not really an answer to your concerns but you can hit F4 or Alt+DownArrow to open a combo box (assuming it’s not a funky custom control that is almost, but not quite, entirely the same as the built-in combo box).

  9. Ant says:

    There’s lots of cases where this "default" behaviour does not work, even in MS apps. Take the VSS Check Out dialog in Visual Studio 2003 that I was using just before reading this article:

    The dialog has three buttons along the bottom – Check Out (heavy default border) Cancel and Help but the focus is in the Comments edit box.

    Hitting return here by the rules, I would expect the default button to be pushed but in fact it just puts a <cr> in the edit box….

  10. Steve Power says:

    Very interesting article, certainly didn’t know I’ve been doing this wrong all these years. I wonder why SetFocus() can’t just do the right thing? At least the help page could remark on the correct method to call if setting focus to a button.

  11. Raymond Chen says:

    Ant: I don’t see what’s wrong. Focus can be on a non-button initially. And multiline edit controls use the WM_GETDLGCODE message (discussed earlier http://weblogs.asp.net/oldnewthing/archive/2003/11/26/55872.aspx ) to indicate that they want the Enter key to go to them and not to the dialog. This is typically what people expect when typing into a text box.

    Dim St Thomas: I guess I don’t see what the problem is. Certainly you can’t send wizard messages when not in a wizard! In the same way you certainly can’t send listbox messages to a button control.

    CPropertySheet is an MFC thing and I know nothing about MFC. What does SetWizardMode correspond to in Win32?

    Steve: SetFocus can’t do "the right thing" because SetFocus is the low level function. The dialog manager is built on top of the window manager, not vice versa.

  12. J. Edward Sanchez says:

    If I’m a user entering text in a multi-line edit control in a dialog, and I see that there is a button with a thick border, I am not going to press Enter to get a newline, and risk prematurely dismissing the dialog. Even though the edit control may be overriding the default Enter key behavior while it has focus, there is no visual indication of that, so there’s no way for me to know without actually trying it — and possibly suffering unwanted consequences.

    Faced with such a situation, I’d just compose the text in Notepad — newlines at all — and then paste it into the dialog.

    Some dialogs work around this problem by using Ctrl+Enter instead of Enter for entering newlines in multi-line edit controls. That works, but it requires an unsightly line of static text explaining how to enter a newline. It’s also error-prone, since it’s not necessarily easy for a user to override his or her automatic Enter keypress to get a newline.

    Suggestion: When a multi-line edit control uses WM_GETDLGCODE to trap the Enter key for its own purposes, it might be a good idea to temporarily remove the "default button" status from the default button, so as not to needlessly confuse the user.

  13. Raymond Chen says:

    That’s an interesting suggestion. Too bad it can’t be done given the current design. (Look at the design of the WM_GETDLGCODE message.)

  14. Dim St Thomas says:

    In one application I worked on we had PropertyPages that were used in both wizard and non-wizard mode PropertySheets. To allow for the wizard mode case we would call CPropertySheet::SetWizardButtons from OnSetActive as it tells you to do in the doc. If this would be the first page of a wizard we would use:

    CPropertySheet* psheet = (CPropertySheet*) GetParent();

    psheet->SetWizardButtons(PSWIZB_NEXT);

    The problem occurs when you use such a page in a non-wizard mode sheet. As soon as the page is selected, the above code is called and it sets the Cancel button as the default button for the sheet.

    To get round this we had to set a flag to tell the page when it was in non-wizard mode so it would not call SetWizardButtons. A CPropertySheet::GetWizardMode would have come in handy here. The CPropertySheet::SetWizardMode doc says it "sets the PSF_WIZARD flag", but PSF_WIZARD doesn’t seem to be defined anywhere.

  15. Ant says:

    Raymond, you say "The default button is typically drawn with a distinctive look (a heavy outline or a different color) and indicates what action the dialog box will take when you hit Enter." and that’s what I’ve always taken will happen. But when the edit box has the focus, it also accepts the enter despite the visual appearance of the default button suggesting otherwise. This is why I think it’s wrong. Action does not match visual cue. If the edit box too away the default outline from the button as other buttons do, all would be well.

  16. Raymond Chen says:

    Right, and my point is that if you look at the WM_GETDLGCODE message you can see why the dialog manager cannot do that. The dialog manager is not psychic.

  17. Dim St Thomas says:

    Raymond wrote "Dim St Thomas: I guess I don’t see what the problem is. Certainly you can’t send wizard messages when not in a wizard! In the same way you certainly can’t send listbox messages to a button control."

    I would compare it with sending a BM_SETCHECK message to a push button. I think most people would not expect this to have any effect. The "problem" seems to be that PSWIZB_NEXT is the same as IDCANCEL and the PropertySheet does not check if it is in wizard mode when handling the PSM_SETWIZARDBUTTONS message, though you could argue that this is the responsibility of the programmer.

    Raymond wrote "CPropertySheet is an MFC thing and I know nothing about MFC. What does SetWizardMode correspond to in Win32?"

    Looking at the MFC source, SetWizardMode adds PSH_WIZARD (not PSF_WIZARD as stated in the SetWizardMode doc) to the PROPSHEETHEADER flags. I also found an undocumented IsWizard function which could be useful.

  18. Seth McCarus says:

    Okay… I’m trying to do a simple thing here, change focus on a dialog to a given button. I’m using

    SendMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)hB[dwDefault], TRUE);

    to set the default button (hB is an array of button HWNDs). Furthermore, I create the default button with the BS_DEFPUSHBUTTON style. I’ve also tried using the DM_SETDEFID message to set the default button. But in each case, the first button created is always the default. I really don’t know what I’m doing wrong. If anyone has a pointer or even wants to e-mail me, see code, etc., I’d really appreciate it.

    Seth (at mccarus dot com).

  19. Horst says:

    Hi, I tried to set focus to combobox edit field in OnInitDialog(){…}.

    SendMessage(hdlg, WM_NEXTDLGCTL, …) didn’t work, but PostMessage(…) :-))

    I’ve tested it under XP and VC 7.1

  20. Raymond Chen says:

    Read the documentation on WM_INITDIALOG for an explanation.

  21. Lu says:

    I actually have a question. I am new to VC++. I have a dialog which has many edit text boxes on it. I want my program to do some calculation after the user enter something into the text box and click else where on the dialog (not a control on the dialog though, just the grey background of the dialog). So I use the LostFocus method, but it works only after I click on the other text box or the other control, not after I click on the dialog. I just want my program to lost the focus when I click on the dialog (not on another control). Is there anyway to do this?

  22. Walk the template items and do what they say.

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