Restating the obvious about the WM_COMMAND message

I'm satisfied with the MSDN documentation for the WM_COMMAND message, but for the sake of mind-numbing completeness, I'm going to state the obvious in the hope that you, dear readers, can use this technique to fill in the obvious in other parts of MSDN.

The one-line summary of the WM_COMMAND message says, "The WM_COMMAND message is sent when the user selects a command item from a menu, when a control sends a notification message to its parent window, or when an accelerator keystroke is translated." In a nutshell, there are three scenarios that generate a WM_COMMAND message, namely the three listed above. You want to think of the menu and accelerator scenarios of the WM_COMMAND message as special cases of the control scenario.

The high-order word of the wParam parameter "specifies the notification code if the message is from a control". What does "control" mean here? Remember that you have to take things in context. The WM_COMMAND message is being presented in the context of Win32 in general, and in the context of the window manager in particular. Windows such as edit boxes, push buttons, and list boxes are commonly called "controls", as are all the window classes in the "common controls library". In the world of the window manager, a "control" is a window whose purpose is to provide some degree of interactivity (which, in the case of the static control, might be no interactivity at all) in the service of its parent window. The fact that the WM_COMMAND is used primarily in the context of dialog boxes further emphasizes the point that the term "control" here is just a synonym for "child window".

What does "notification code" mean here? Control notification codes are arbitrary 16-bit values defined by the control itself. By convention, they are named xxN_xxxx, where the "N" stands for "notification". Be careful, however, not to confuse this with notification codes associated with the WM_NOTIFY message. Fortunately, every notification code specifies in its documentation whether it arrives as a WM_COMMAND notification or a WM_NOTIFY notification. A modern control designer is more likely to use WM_NOTIFY notifications since they allow additional information to be passed with the notification. The WM_COMMAND message, by comparison, passes only the notification itself; the other parameters to the WM_COMMAND message are forced, as we'll see below. If WM_NOTIFY is superior to WM_COMMAND, why do some controls use WM_COMMAND? Because WM_NOTIFY wasn't available until Windows 95. Controls that were written prior to Windows 95 had to content themselves with the WM_COMMAND message.

"If the message is from an accelerator, this value [the high-order word of the wParam parameter] is 1." Remember, we're still in the context of the window manager, and particular in the context of the WM_COMMAND message. The accelerator here refers to messages generated by the call to TranslateAccelerator in the message loop.

"If the message is from a menu, this value is zero." If the WM_COMMAND mesage was triggered by the user selecting an item from a menu, then the high-order word of the wParam is zero.

The low-order word of the wParam parameter "specifies the identifier of the menu item, control, or accelerator." The identifier of a menu item or accelerator is the command code you associated with it in your menu or accelerator template or (in the case of a menu item) when you manually created the menu item with a function like InsertMenuItem. (You probably named your menu item identifiers and accelerator identifiers IDM_something.) The identifier of a control is determined by the creator of the control; recall that the hMenu parameter to the CreateWindow and CreateWindowEx functions is treated as a child window identifier if you're creating a child window. It is that identifier that the control identifier. (You can retrieve the identifier for a control by calling the GetDlgCtrlID function.)

Finally, the lParam parameter is the "handle to the control sending the message if the message is from a control. Otherwise, this parameter is NULL." If the notification is generated by a child window (with a notification code appropriate for that child window, obviously), then that child window handle is passed as the lParam. If the notification is generated by an accelerator or a menu, then the lParam is zero.

Notice that nearly all of the parameters to the WM_COMMAND message are forced, once you've decided what notification you're generating.

If you are generating a notification from a control, you must pass the notification code in the high word of the wParam, the control identifier in the low word of the wParam, and the control handle as the lParam. In other words, once you've decided that the hwndC window wants to send a CN_READY notification, you have no choice but to type

SendMessage(GetParent(hwndC), WM_COMMAND,
            MAKEWPARAM(GetDlgCtrlID(hwndC), CN_READY),

In other words, all control notifications take the form

SendMessage(GetParent(hwndC), WM_COMMAND,
            MAKEWPARAM(GetDlgCtrlID(hwndC), notificationCode),

where hwndC is the control generating the notification and notificationCode is the notification code. Of course, you can use PostMessage instead of SendMessage if you would rather post the notification rather than sending it.

The other two cases (accelerators and menus) are not cases you would normally code up, since you typically let the TranslateAccelerator function deal with accelerators and let the menu system deal with menu identifiers. But if for some reason, you wanted to pretend that the user had typed an accelerator or selected a menu item, you can generate the notification manually by following the rules set out in the documentation.

// simulate the accelerator IDM_WHATEVER
SendMessage(hwnd, WM_COMMAND,

Here, hwnd is the window that you want to pretend was the window passed to the TranslateAccelerator function, and IDM_WHATEVER is the accelerator identifier.

Simulating a menu selection is exactly the same, except that (according to the rules above), you set the high-order word of the wParam to zero.

// simulate the menu item IDM_WHATEVER
SendMessage(hwnd, WM_COMMAND,

Here, hwnd is the window associated with the menu. A window can be associated with a menu either by being created with the menu (having passed the menu handle to the CreateWindow or CreateWindowEx function explicitly, or having it done implicitly by including it with the class registration) or by having been passed explicitly as the window parameter to a function like TrackPopupWindow.

One significant difference between the accelerator/menu case and the control notification case is that accelerator and menu identifiers are defined by the calling application, whereas control notifications are defined by the control.

You may have noticed the opportunity to "pun" the control notification codes. If a control defines a notification code as zero, then it will "look like" a menu item selection, since the high-order word of the wParam in the case of a menu item selection is zero. The button control takes advantage of this pun:

#define BN_CLICKED          0

This means that when the user clicks a button control, the WM_COMMAND message that is generated "smells like" a menu selection notification. You probably take advantage of this in your dialog procedure without even realizing it.

(The static control also takes advantage of this pun:

#define STN_CLICKED         0

but in order for the static control to generate the STN_CLICKED notification, you have to set the SS_NOTIFY style.)

I stated at the start that the accelerator and menu scenarios are just special cases of the control scenario. If you take the pieces of the WM_COMMAND message apart, you'll see that they fall into two categories:

  • What happened? (Notification code.)
  • Whom did it happen to? (Control handle and ID.)

In the case of a menu or an accelerator, the "What happened?" is "The user clicked on the menu (0)" or "The user typed the accelerator (1)". The "Whom did it happen to?" is "This menu ID" or "This accelerator ID". Since the notification is not coming from a control, the control handle is NULL.

I apologize to all you Win32 programmers for whom this is just stating the obvious.

Now that you're an expert on the WM_COMMAND message, perhaps you can solve this person's problem.

Comments (27)
  1. AsmGuru62 says:

    I am not sure, but "(HMENU) 0" is suspicious. The control ID (child window ID) should not be zero.

  2. KiwiBlue says:


    I’ve written Windows code, and I don’t know the particulars of WM_COMMAND.


    Looks like Windows code you’ve writen was a bunch of "declare sub" in VB, dude.

  3. Gabe says:

    JD: There are 2 basic types of questions that beginners tend to ask.

    One is "How do I do X?" In that case, the user knows what he wants to do but simply doesn’t know where to look, and a quick redirect to the correct term or page of documention (i.e. "look up WM_COMMAND in MSDN") will solve the problem. Even with 20 years of experience I still need to ask these questions when learning a new environment or language.

    The other question is "Why isn’t X working?" Sometimes X is difficult, even for non-beginners. In this case though, he knew that WM_COMMAND was what he needed, he just seemingly ignored all the documentation for it.

    What I’m interested in, though, is how this differed in 16-bit Windows. Back then WPARAMs were actually word-length, so they didn’t have a high word. How did WM_COMMAND work, or what was it used for, then?

  4. lolerskatez! says:

    Im 13373r than j00 a11! Wargh check out ma size of ma controlz!


  5. KiwiBlue says:

    Gabe, in Win16 32-bit LPARAM contained both control handle and notification code – it was possible because handles were 16-bit. LOWORD(lParam) was handle, HIWORD(lParam) was notification code.

  6. Mike says:

    Give a man a fish and he’ll eat for a day.

    Try to teach a man to fish and he’ll bitch at you for not just giving him the fish.

  7. Daniel says:


    "Try to teach a man to fish and he’ll bitch at you for not just giving him the fish."

    Thanks for the laugh! You are so right.

  8. 8 says:

    You’ve just made it more confusing, it doesn’t happen much, and I don’t like to admit it, but this time the MSDN docs are more clear then yours.

    Now that we’re restating the obvious anyway and I’m confused… That the WM_COMMAND from a clicked button "smells like" a menu selection is very useful. But to make it almost exactly the same instead of havig it smell like it, the same ID?_* has to be used for both the accelerator and the button control… Right?

    And I could not find any information on TrackPopupWindow. I tried this:

  9. KiwiBlue says:

    I guess TrackPopupWindow should be TrackPopupMenu[Ex] instead.

  10. Jay B says:

    You know… sometimes even the blatantly obvious can stare you in the face and you overlook it.  It happens to everyone at one time or another.

    Sometimes it just helps to have a fresh set of eyes look at something.  Sometimes its good to leave a problem alone for a bit and come back to it with your own fresh set of eyes.

  11. Chris Moorhouse says:

    I know jack about Win32, but I love trying to solve these. Who needs the Times’ crossword when you have Raymond and Larry? The downside is that I am usually the first to get caught in labrynthine snarl that is MSDN.

    Three failure points occur to me:

    1)Russell expects the control ID in the notification to be 0, and it’s not. (HMENU) 0 may somehow transform into 1000, I don’t know and I’m having trouble finding out. Looks like a cast to me. At any rate, while there may be reasons not to use 0 here, it doesn’t say so in the linked documentation.

    2)Russell is expecting the ID to appear in the HIWORD of the wParam. It comes in the LOWORD. This is mentioned in the linked documentation, and Raymonds reaction seems to imply that this is the problem.

    3)Russell is, as he states, listening in the wrong place for this message. He’s trying to have it recieved by the combobox, when it’s the combobox’s parent that should be recieving it. This isn’t terribly clear from the linked documentation; it seems to be saying that WM_COMMAND is a seperate message that is sent when a control sends some other message to it’s parent.

    If you read the rest of the thread, #2 turns out not to be the problem, that is just a typo in Russell’s post (or so he claims). #3 appears to be the issue. He would have found this out if he’d read the docs for CBN_SELENDOK more carefully, but that’s not what Raymond posted. Raymond is either being overly subtle about his sarcasm (very likely), or he didn’t read the whole thread.

  12. The problem is #3, as Norman Bullen pointed out later in the thread:

    The very first sentence of the WM_COMMAND documentation says that WM_COMMAND is sent "when a control sends a notification message to its parent window". If you think this could be made clearer, please elaborate.

  13. But it isn’t "the" notification message sent by a control to its parent. There are other notifications such as WM_NOTIFY and WM_PARENTNOTIFY.

    And how would that fit into the rest of the sentence? Don’t forget the menu and accelerator scenarios in the intro blurb.

  14. Chris Moorhouse says:

    "A telegram is sent when we draw your ticket, when we send a telegram to the winner after judging all entries, or when all other contestants are eliminated."

    How is it obvious to us that the subject of the sentence (the telegram in the main clause) is the same thing as the subject(?) of the second conditional clause (the telegram to the winner)? If they are indeed the same thing, why mention them seperately? The fact that that sentence in the documentation metions the word "message" twice similarily raises the possibility, in my mind, that we’re talking about two seperate messages.

    I get the feeling that the obviousness of this solution to Russell’s problem lies, as you say, in the context of the Win32 API. Someone familiar with its ins and outs is likely saying "Of course it’s the same message! What other message could there possibly be?" at this point. But where else can someone with less experience turn to when unfamiliar with this message and its context?

    What Russell did miss was the third sentence of the documentation of the CBN_SELENDOK, which more clearly indicates that the one is wrapped within the other. That’s what cleared this up for me, even before I read the remainder of the thread.

    I feel really nervous about offering advice here, but since you asked, I’m gonna. It may be possible to reduce confusion by dropping the word ‘message’ in reference to the notification enclosed in the WM_COMMAND (or even replace ‘message’ with ‘code’, so as to match the documentation in the wParam parameter), but there may be naming conventions or rules in place that would make this a less than optimal solution. We might do better to use an extra sentence or two instead.

    "The WM_COMMAND message is sent when the user selects a command item from a menu or when an accelerator keystroke is translated. It is also the message sent to a control’s parent window when certain actions are taken on that control; in this case the WM_COMMAND message encloses a notification code related to that action."

    This probably violates all sorts of naming specs and conventions. Just thought I’d try.

  15. As I was learning this from Petzold, I had to draw myself some sort of spreadsheet to understand it.

    My wm_command handler starts like this:

    static int  StdOnWM_Command (HWND hwnd, WPARAM wParam, LPARAM lParam)


      short       wNotifyCode = HIWORD (wParam);   // notification code, 0 if menu, 1 if accel

      short       item_id     = LOWORD (wParam);   // item, control, or accelerator identifier

      HWND        ctlHwnd     = (HWND)  lParam;    // handle of control, NULL if menu

    plus, there’s something I did not expect until it bite me: edit box recieves notifications during its creation, but if you don’t have everything set up and initialized  properly at the time, it can produce random crashes.

    It drove me crazy!

  16. Igor Delovski says:

    … edit box recieves above mentioned notification on WM_CREATE only if it has initial text.

  17. JD says:


    OMG that Russell didn’t even *read* MSDN on WM_COMMAND! It says right there "The high-order word specifies the notification code if the message is from a control" and he’s passing (HMENU) 0!  Haha! Talk about missing the obvious!


    Is that the response you’re looking for? Raymond, you’re an … well you clearly demonstrate the old-skool Win32 devs who look down on devs who make mistakes.

    Very little of what you wrote above is "obvious" in the sense that normal people in this world use the term. Perhaps you have been working with the Windows APIs for over a decade and this is second nature to you, and you have had to debug and diagnose problems due to developers who made these kinds of mistakes, and that has made you insensitive.

    I’ve written Windows code, and I don’t know the particulars of WM_COMMAND. The info you laid out above is useful, and not directly obvious from the MSDN article. A nice table illustrating the three scenarios (control, menu, accelerator) and what they fill in the params with would be an improvement to the article and make it more ‘obvious’.

    Sure it’s easy to say "RTFM n00b, it’s right freaking there" and bemoan how these people should be doing their homework and *thinking* through the problem. But from the question it seems that Russell (‘this person’) did do some due diligence and has simply barked up the wrong tree.

    Your insight here was welcome and use, the attitude that this is belaboring the obvious (and implying that it’s a waste of your time to impart such wisdom directly to the plebes) is odious and reflects badly on you.

    Why does it make you feel better to hand out some condescention (OMG did I spell that wrong?) with the fishing pole when someone asks for a fish? I guess you’re the drill sargeant (lol learn2spell, n00b) whose rough on his men but earns their respect? Do continue.

  18. Miral says:

    How about "WM_COMMAND *is* the notification message sent by a control to its parent window".  No ambiguity there.  There’s definitely ambiguity in the MSDN statement, although I think most people will parse it correctly.

  19. Norman Diamond says:

    Simulating a menu selection is exactly the

    > same, except that (according to the rules

    > above), you set the high-order word of the

    > wParam to zero.

    That’s obvious if you have some way of knowing which of the above rules is the obvious one to add an invisible phrase to.  If you guess wrong about which rule, you might not even add an invisible phrase, you might just obey the rule where you have to say which control is sending the message.  The invisible phrase that needs adding is exactly this, "Simulating a menu selection, you set the high-order word of the wParam to zero".

    Another needed addition to the page on WM_COMMAND would say "(except when the notification is sent by WM_NOTIFY or other message, as described in documentation for the individual control)".

    Also a bunch of pages on notification-style notifications need some editing in order to distinguish the constant as a constant, and say what typedef identifier has to be used in declaring a variable.  I copied some stuff from the MSDN page on one notification (forgot which one, sorry) and it wouldn’t compile because the stated syntax resulted in using a constant as a typename.  I never did find out what the correct name of the typedef identifier was.  I gave up and found some other way to code around it.  Later I noticed a bunch of descriptions of notifications suffer from the same writing style.

  20. Norman, please be specific. Where do you want the parenthetical remark added? Where are the "bunch of pages"?

  21. JD says:

    It doesn’t have to be a parenthetical. Just explain as you did here. Or put in a small table:


    WPARAM (high part): 0

    WPARAM (low part): IDM code for menu

    LPARAM: 0

    MESSAGE SOURCE: Accelerator

    WPARAM (high part): 1

    WPARAM (low part): IDM code for accelerator

    LPARAM: 0


    WPARAM (high part): notification code (defined by control)

    WPARAM (low part): Control ID

    LPARAM: handle to control window

    Quibble about the wording if you want, but a table makes this clearer that there are threee cases and which params are what. Parsing it is much easier than parentheticals or trying to change a word or two.  

  22. Norman Diamond says:

    Friday, March 03, 2006 11:43 AM by oldnewthing

    > Norman, please be specific. Where do you

    > want the parenthetical remark added?

    Thank you for your interest.  I think that the MSDN page on WM_COMMAND is not as obvious as you think it is, for the reasons I stated.  I already did run into problems and got help from MVPs (or maybe one MVP and one other ordinary newsgroup participant).  They explained that when simulating a menu invocation we have to disobey the instructions about saying which control sent the command, we have to make it look as though the command came from the menu not from the control.  When reading your posting and seeing that you cited someone else with a variation on the same problem, I was relieved to see I wasn’t the only one.

    > Where are the "bunch of pages"?

    Various notification messages from individual controls.  MSDN has subsections on individual controls, including subsubsections on the messages, notifications, structures, etc. for each individual controls.  Each notification gets its own page.  A bunch of them confuse constants (a numeric value saying which kind of notification) with datatypes (what kind of structure is pointed to by a pointer), and in one case I never could figure out what the datatype was supposed to be.

  23. "when simulating a menu invocation we have to disobey the instructions about saying which control sent the command, we have to make it look as though the command came from the menu not from the control."

    Um, the rule about which control sent the command doesn’t apply if you want to simulate a menu action; it applies if you want to simulate a control, which you aren’t. If you want to simulate a menu, then follow the menu rules. You’re not disobeying anything.

  24. Norman Diamond says:

    } lParam

    } Handle to the control sending the message if

    } the message is from a control. Otherwise,

    } this parameter is NULL.

    Handle to the control sending the message if the message is from a control (except if the message is a simulated menu command even when it was sent from a control).  Otherwise (including if the message is a simulated menu command sent from a control) this parameter is NULL.

    How can you say that the page makes that obvious?

  25. 8 says:

    Because it says what it is, how it is?

    IMHO it’s safe for the docs to assume that the reader writes the window procedures and Send or Post Message calls, or atleast knows thoroughly about them. And if you don’t care about a certain WM_COMMAND you received, return FALSE.

  26. Norman Diamond says:

    Friday, March 03, 2006 11:43 AM by oldnewthing

    > Where are the "bunch of pages"?

    Since I needed to look at NMHDR yesterday, it reminded me.  There are a bunch of structures that start with an NMHDR, and depending on the kind of notification, the application should cast the pointer to a type that points to the larger structure.  There are probably identifiers that are typedef’ed for the structure and pointer types, but MSDN isn’t saying.  I couldn’t figure out what to cast it to.

Comments are closed.

Skip to main content