What is the mysterious fourth message box button?


When you call the MessageBox function, you pass flags specifying which of a fixed set of button patterns you want (for example, Yes/No and OK/Cancel) and which button you want to be the default (MB_DEFBUTTON1 through MB_DEFBUTTON4.)

Wait a second. What's with this MB_DEFBUTTON4? None of the button patterns are four-button patterns. The highest number of buttons you can specify is three: Abort/Retry/Ignore. How can you set a nonexistent button to be the default?

Let's do some header file spelunking. The flag for this magical fourth button is defined here:

#define MB_DEFBUTTON1               0x00000000L
#define MB_DEFBUTTON2               0x00000100L
#define MB_DEFBUTTON3               0x00000200L
#if(WINVER >= 0x0400)
#define MB_DEFBUTTON4               0x00000300L
#endif /* WINVER >= 0x0400 */

Aha, the magic fourth button was added in WINVER 4.0. Therefore, whatever the fourth button is, it was introduced when WINVER == 0x0400. Let's see what other message box flags were introduced then:

#define MB_OK                       0x00000000L
#define MB_OKCANCEL                 0x00000001L
#define MB_ABORTRETRYIGNORE         0x00000002L
#define MB_YESNOCANCEL              0x00000003L
#define MB_YESNO                    0x00000004L
#define MB_RETRYCANCEL              0x00000005L
#if(WINVER >= 0x0500)
#define MB_CANCELTRYCONTINUE        0x00000006L
#endif /* WINVER >= 0x0500 */


#define MB_ICONHAND                 0x00000010L
#define MB_ICONQUESTION             0x00000020L
#define MB_ICONEXCLAMATION          0x00000030L
#define MB_ICONASTERISK             0x00000040L

#if(WINVER >= 0x0400)
#define MB_USERICON                 0x00000080L
#define MB_ICONWARNING              MB_ICONEXCLAMATION
#define MB_ICONERROR                MB_ICONHAND
#endif /* WINVER >= 0x0400 */

#define MB_ICONINFORMATION          MB_ICONASTERISK
#define MB_ICONSTOP                 MB_ICONHAND

#define MB_DEFBUTTON1               0x00000000L
#define MB_DEFBUTTON2               0x00000100L
#define MB_DEFBUTTON3               0x00000200L
#if(WINVER >= 0x0400)
#define MB_DEFBUTTON4               0x00000300L
#endif /* WINVER >= 0x0400 */

#define MB_APPLMODAL                0x00000000L
#define MB_SYSTEMMODAL              0x00001000L
#define MB_TASKMODAL                0x00002000L
#if(WINVER >= 0x0400)
#define MB_HELP                     0x00004000L // Help Button
#endif /* WINVER >= 0x0400 */

#define MB_NOFOCUS                  0x00008000L
#define MB_SETFOREGROUND            0x00010000L
#define MB_DEFAULT_DESKTOP_ONLY     0x00020000L

#if(WINVER >= 0x0400)
#define MB_TOPMOST                  0x00040000L
#define MB_RIGHT                    0x00080000L
#define MB_RTLREADING               0x00100000L
#endif /* WINVER >= 0x0400 */

#ifdef _WIN32_WINNT
#if (_WIN32_WINNT >= 0x0400)
#define MB_SERVICE_NOTIFICATION          0x00200000L
#else
#define MB_SERVICE_NOTIFICATION          0x00040000L
#endif
#define MB_SERVICE_NOTIFICATION_NT3X     0x00040000L
#endif

We can discount the flags like MB_ICONWARNING which are just alternate names for existing flags, as well as MB_SERVICE_NOTIFICATION which already existed but with a different value. This leaves the following:

#define MB_USERICON                 0x00000080L
#define MB_HELP                     0x00004000L // Help Button
#define MB_TOPMOST                  0x00040000L
#define MB_RIGHT                    0x00080000L
#define MB_RTLREADING               0x00100000L

Of these flags, MB_USERICON affects the icon, and MB_TOPMOST, MB_RIGHT and MB_RTLREADING affect the dialog box's position and layout; none of them affect the buttons. But wait, there's MB_HELP. Ah, that flag "adds a Help button to the message box." That's our magical fourth button! Let's celebrate by showing a four-button message box with the default set to the fourth button:

#include <windows.h>

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                   LPSTR lpCmdLine, int nShowCmd)
{
  return MessageBox(NULL, TEXT("Four buttons!"), TEXT("Title"),
            MB_ABORTRETRYIGNORE | MB_HELP | MB_DEFBUTTON4);
}
Comments (25)
  1. Bill says:

    Four buttons ought to be enough for anyone.

  2. asf says:

    /me hopes Win7 brings back the context help that was removed from everything in Vista for no reason

  3. asf is an ahole says:

    Jesus Freaking Christ! Is there any topic that doesn’t lead to some Vista related whine?

  4. ton says:

    Good post Raymond. Its good to learn more about deciphering these obscure macro statements littered throughout the header files.

  5. Bill says:

    asf is an ahole: No.

    P.S.  Vista kicked my dog.

  6. Santhosh says:

    Won’t just MB_ABORTRETRYIGNORE | MB_HELP produce 4 buttons?

  7. C says:

    Compiled the WinMain code on XP.  Clicking the "Help" button does not do anything.  The dialog box remains visible to be clicked again.  Should this happen or did I miss something?  Clicking any of the other three buttons exits the dialog.

  8. John says:

    The documentation exists for a reason…

  9. benjamin says:

    Don’t touch that button!

    That shiny, red, candy-like button!

    It’s the history eraser button!

  10. chrismcb says:

    @Santhosh yes, but then you won’t get a chance to use the oh so cool "MB_DEFBUTTON4" which is what started the post.

  11. BillWert says:

    @C

    MB_HELP is documented as sending a WM_HELP message to your program. Since there is nothing to catch that message, it does nothing. The other buttons return the ID value of that button, dismissing the message box.

  12. Dan says:

    Santhosh: MB_DEFBUTTONN makes the Nth button the default button in the dialog (the default control, the one that is pushed when you press enter without changing control focus first… also the one that is accidentally pressed when the dialog pops up while you are typing something).

  13. Kuijf says:

    Interesting to learn how to figure these things out. But it makes me wonder however, in which situation you would like the Help button to be the default button of your message box.

  14. Shog9 says:

    Kuijf: as Dan noted, the default button is the one that is most easy to accidentally trigger when a message box pops up unexpectedly. For most evil programmers then, it’s enough to periodically throw up a "Reformat harddrive [yes] [no]" box, with [yes] as the default – they’ll cackle evilly (as is their nature) and go to bed with visions of paranoid users dancing in their (evil) heads…

    But the *really* evil programmers, the green grinch-like ones that live in caves overlooking Userville and never, ever sleep much less dream… Those programmers need something better: a message box that pops up offering to format your harddrive AND BRINGS UP WinHlp32.exe WHEN YOU ACCIDENTALLY HIT THE SPACEBAR!

  15. Bob says:

    Not a whine, but was this a "secret?"

    Even VB6 has vbMsgBoxHelpButton defined and documented.

  16. required says:

    @Bob: it isn’t a secret. That’s what Raymond is demonstrating here, that the necessary information is in the header file. It’s only a "secret" if you can’t see it.

  17. Larry Lard says:

    [my emphasis]

    > That’s our magical fourth button! **Let’s celebrate** by showing a four-button message box with the default set to the fourth button:

    Raymond, we love you.

  18. Bob says:

    Gotcha.  That and I suppose the relationship between MB_DEFBUTTON4 and which button it makes "default."

    Duh, sorry.

  19. kero says:

    Hm, why MB_HELP – "mysterious"??

    Real mysterious flag  –  MB_TOPMOST!

    On  WinXP  MB_TOPMOST != MB_SERVICE_NOTIFICATION,  but following Message Box –

    
    ThreadProc proc lparam:LPARAM
    
    invoke MessageBox,0,offset _msg,0,MB_TOPMOST
    
    ret
    
    ThreadProc endp
    
    start:
    
    invoke CreateThread,0,0,offset ThreadProc,0,0,0
    
    invoke CloseHandle,eax
    
    invoke Sleep,10
    
    invoke ExitProcess,0
    
    end start
    
    
    • belongs to CSRSS!  

    Of course, MB_TOPMOST = MB_SERVICE_NOTIFICATION_NT3X, but – WinXP…

  20. MC says:

    We don’t need no stinkin’ documentation!

    RTFHF!

  21. Yuhong Bao says:

    "Of course, MB_TOPMOST = MB_SERVICE_NOTIFICATION_NT3X"

    Basically what happened here is that Win95 introduced MB_TOPMOST, and then when they tried to port the Win95 UI to NT, they found that it conflicted with MB_SERVICE_NOTIFICATION, thus MB_SERVICE_NOTIFICATION had to be reassigned to a different value. The old MB_SERVICE_NOTIFICATION value was renamed MB_SERVICE_NOTIFICATION_NT3X.

  22. kero says:

    2 Yuhong Bao

    MSDN – "MessageBox function":

    MB_SERVICE_NOTIFICATION_NT3X

    Windows NT/2000/XP: This value corresponds to the value defined for MB_SERVICE_NOTIFICATION for Windows NT version 3.51.

    Windows NT/2000/XP: The value of MB_SERVICE_NOTIFICATION changed starting with Windows NT 4.0.

    Windows NT 4.0 provides backward compatibility for pre-existing services by mapping the old value to the new value in the implementation of MessageBox.

    This mapping is only done for executables that have a version number less than 4.0, as set by the linker.

    To build a service that uses MB_SERVICE_NOTIFICATION, and can run on both Microsoft Windows NT 3.x and Windows NT 4.0, you can do one of the following.

    At link-time, specify a version number less than 4.0

    At link-time, specify version 4.0. At run-time, use the GetVersionEx function to check the system version.

    Then when running on Windows NT 3.x, use MB_SERVICE_NOTIFICATION_NT3X; and on Windows NT 4.0, use MB_SERVICE_NOTIFICATION.

    You see, I don’t build a service, I just want MB-window with WS_EX_TOPMOST style, so run Message Box with flag  MB_TOPMOST (on WinXP, not NT3.x !).

    If I run MessageBox from initial thread – then OK: Message Box belongs to my application. But if I run it from 2nd thread – Message Box belongs to CSRSS…

    Where MSDN’s warning ?! :)

  23. Yuhong Bao says:

    "If I run MessageBox from initial thread – then OK: Message Box belongs to my application. But if I run it from 2nd thread – Message Box belongs to CSRSS…"

    Because of the aformentioned conflict, NT 4.0 and later is forced to guess whether you by using the flag 0x00040000L mean MB_TOPMOST or MB_SERVICE_NOTIFICATION for compatiblity with pre-NT 4.0 services. Obviously part of the guess include the thread you are calling MessageBox from, not surprising since usually the main thread in a service is the service dispatcher thread, while all the real work in a service happens in other threads.

  24. kero says:

    NT 4.0 and later is forced to guess whether you by using the flag 0x00040000L mean MB_TOPMOST or MB_SERVICE_NOTIFICATION for compatiblity with pre-NT 4.0 services.

    Then it is infelicitous algo; it seems to me the system must remember its own version and behave oneself suitable :)

  25. Anonymous says:

    Thanks to benjamin for adding a quality Ren and Stimpy reference to an otherwise unremarkable post.

Comments are closed.

Skip to main content