My Photo
Location: Downham Market, Norfolk, United Kingdom

Sunday, November 05, 2006

Some more examples of shortcomings in the Windows API documentation have been causing me grief recently. The first concerns the WM_CREATE notification and what happens when you return -1 from it. According to the docs "the window is destroyed and the CreateWindowEx or CreateWindow function returns a NULL handle"; what it doesn't say is that the WM_DESTROY handler is called in the process! This is not a trivial omission because you are very likely to include in the WM_DESTROY handler some 'cleanup' code which you may not necessarily want to execute if WM_CREATE has been aborted. For example - and this is what affected me - WM_CREATE might launch a worker thread that WM_DESTROY terminates; if the thread was never created (because WM_CREATE was aborted beforehand) then WM_DESTROY might wait forever for the non-existent thread to exit.

Fortunately a solution is to hand in the form of the WM_NCCREATE notification: this is called before WM_CREATE and again can be aborted (this time by returning FALSE) with the result that "the CreateWindow or CreateWindowEx function will return a NULL handle". Note that it doesn't say "the window is destroyed" and indeed the WM_DESTROY handler is not called in this case. So by moving some of the initialisation from WM_CREATE to WM_NCCREATE you can abort it without activating WM_DESTROY. But yet again the docs let you down: there is no hint that processing WM_NCCREATE will have any untoward side-effects but in fact it causes the window title not to appear! To get the normal window title you must exit from WM_NCCREATE via DefWindowProc.

The second case where the API documentation leaves a lot to be desired is the MenuItemFromPoint function. This "determines which menu item, if any, is at the specified location" and must be passed the "Handle to the window containing the menu", the handle of the menu itself and the location to test: "If hMenu specifies a menu bar, this parameter is in window coordinates. Otherwise, it is in client coordinates". Already we are led astray, at least in the case of a popup menu. Firstly, the "window containing the menu" isn't the main window containing the menu bar - it's the actual menu itself (you'd be forgiven for not knowing that a popup menu is a window!). Secondly you have to pass screen coordinates always, not "window coordinates" (whatever they are) or client coordinates!

This is a pretty comprehensive set of errors, but it gets worse. How do we find the window handle for a popup menu? This is quite an elusive beast and there are no direct ways of discovering it as far as I know. The only methods I am aware of are to use "WindowFromPoint" if you know a point within the popup menu (which you are likely to if you intend to use MenuItemFromPoint!) or "GetTopWindow" (with a NULL parameter) if you know that the menu has just been displayed. This latter method isn't reliable if the menu item you are interested in opens a sub-menu - which will become the 'top' window if you pause long enough with the mouse over the item.

At least if you do go to the trouble of finding the menu's window handle you can use it to find the menu handle itself via the MN_GETHMENU message, although according to Microsoft that only works on Windows 2000 or later. But that's wrong too - did you guess? It actually works fine right back to Windows 95 according to my tests. So once you've gleaned all this hard-to-find information you can discover the menu item just from the screen coordinates:

hwnd = WindowFromPoint(x, y) ;
hmenu = SendMessage(hwnd, MN_GETHMENU, 0, 0);
item = MenuItemFromPoint(hwnd, hmenu, x, y);

Microsoft takes pity on us in recent versions of Windows by allowing us to pass NULL as the "Handle to the window containing the menu", thereby making it unnecessary to discover it by nefarious means; but just to rub salt in the wounds they get the documentation of this wrong too by stating that it works in "Windows 98/Me and Windows 2000/XP" whereas in fact it only works in Windows 2000/XP. Don't you just love it?

The sting in the tail, just for good measure, is that the return values from MenuItemFromPoint are incorrectly documented too. According to the docs it "Returns the zero-based position of the menu item at the specified location or -1 if no menu item is at the specified location" but I know for a fact that values -3 and -4 can be returned, although precisely what they signify remains a mystery.