{"id":11453,"date":"2011-02-18T07:00:00","date_gmt":"2011-02-18T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2011\/02\/18\/wm_nchittest-is-for-hit-testing-and-hit-testing-can-happen-for-reasons-other-than-the-mouse-being-over-your-window\/"},"modified":"2011-02-18T07:00:00","modified_gmt":"2011-02-18T07:00:00","slug":"wm_nchittest-is-for-hit-testing-and-hit-testing-can-happen-for-reasons-other-than-the-mouse-being-over-your-window","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20110218-00\/?p=11453","title":{"rendered":"WM_NCHITTEST is for hit-testing, and hit-testing can happen for reasons other than the mouse being over your window"},"content":{"rendered":"<p>\nThe <code>WM_NC&shy;HIT&shy;TEST<\/code> message is sent to your window\nin order determine what part of the window corresponds to a\nparticular point.\nThe most common reason for this is that\nthe mouse is over your window.<\/p>\n<ul>\n<li>\nThe default <code>WM_SET&shy;CURSOR<\/code> handler\nuses the result of <code>WM_NC&shy;HIT&shy;TEST<\/code>\nto figure out what type of cursor to show.\nfor example, if you return <code>HT&shy;LEFT<\/code>,\nthen <code>Def&shy;Window&shy;Proc<\/code>\nwill show the <code>IDC_SIZE&shy;WE<\/code> cursor.\n<\/li>\n<li>\nIf the user clicks the mouse,\nthe default <code>WM_NC&shy;LBUTTON&shy;DOWN<\/code> handler\nuses the result of <code>WM_NC&shy;HIT&shy;TEST<\/code> to figure out\nwhere on the window you clicked.\nFor example, if you return <code>HT&shy;CLOSE<\/code>, then it will\nact as if the user clicked on the Close button.\n<\/li>\n<\/ul>\n<p>\nAlthough <code>WM_NC&shy;HIT&shy;TEST<\/code> triggers most often\nfor mouse activity, that is not the only reason why\nsomebody might want to ask,\n&#8220;What part of the window does this point correspond to?&#8221;\n<\/p>\n<ul>\n<li>\nThe <code>Window&shy;From&shy;Point<\/code> function uses\n<code>WM_NC&shy;HIT&shy;TEST<\/code> in its quest to figure\nout which window is under the point you passed in.\nIf you return <code>HT&shy;TRANSPARENT<\/code>,\nthen it will skip your window and keep looking.\n<\/li>\n<li>\nDrag\/drop operations use the result of\n<code>WM_NC&shy;HIT&shy;TEST<\/code> to figure out what part of the\nwindow you are dragging over.\n<\/li>\n<li>\nAccessibility tools use the result of\n<code>WM_NC&shy;HIT&shy;TEST<\/code> to help the user understand\nwhat&#8217;s on the screen.\n<\/li>\n<li>\nAnybody can use the result of\n<code>WM_NC&shy;HIT&shy;TEST<\/code> to see how your window is laid out.\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2003\/10\/27\/55461.aspx\">\nWe used it a few years ago<\/a>\nto detect a right-click on the caption button.\n<\/li>\n<\/ul>\n<p>\nConsider a program that wants to beep\nwhen the mouse is over the Close button.\nThis is an artificial example, but you can use your imagination\nto come up with more realistic ones,\nlike showing a custom mouseover animation or\ndisplaying a balloon tip if the document is unsaved.\nI chose beeping because it requires less code;\notherwise, all the details of its implementation would distract\nfrom the point of the example.\n<\/p>\n<p>\nStart with\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2003\/07\/23\/54576.aspx\">\nthe scratch program<\/a>\nand make the following changes:\n<\/p>\n<pre>\nBOOL g_fInCloseButton = FALSE;\nvoid EnterCloseButton(HWND hwnd)\n{\n if (g_fInCloseButton) return;\n g_fInCloseButton = TRUE;\n MessageBeep(-1); \/\/ obviously something more interesting goes here\n TRACKMOUSEEVENT tme = { sizeof(tme), TME_NONCLIENT | TME_LEAVE, hwnd };\n TrackMouseEvent(&amp;tme);\n}\nvoid LeaveCloseButton(HWND hwnd)\n{\n if (g_fInCloseButton) {\n  \/\/ stop animation, remove balloon, etc.\n  g_fInCloseButton = FALSE;\n }\n}\n<i>\/\/ This code is wrong - see text\nUINT OnNcHitTest(HWND hwnd, int x, int y)\n{\n UINT ht = FORWARD_WM_NCHITTEST(hwnd, x, y, DefWindowProc);\n if (ht == HTCLOSE) {\n  EnterCloseButton(hwnd);\n } else {\n  LeaveCloseButton(hwnd);\n }\n return ht;\n}<\/i>\nHANDLE_MSG(hwnd, WM_NCHITTEST, OnNcHitTest);\ncase WM_NCMOUSELEAVE:\n LeaveCloseButton(hwnd);\n break;\n<\/pre>\n<p>\nWe keep track of whether or not the mouse is in the close button\nso that we don&#8217;t double-start the animation or double-cancel it.\n(For us, this keeps us from beeping\nwhen the mouse moves around <i>within<\/i> the Close button.)\nWhen the mouse leaves the close button&mdash;either because it\nmoved to another part of the window or because it left the\nwindow\nentirely&mdash;we reset the flag.\n<\/p>\n<p>\nWhen you run this program, it pretty much behaves as intended.\nBut that&#8217;s because we haven&#8217;t tried anything interesting yet.\n<\/p>\n<p>\nMerge in the changes from our\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2004\/12\/06\/275659.aspx\">\nsample drag\/drop program<\/a>,\nso now you have a program that both performs drag\/drop and which\nhas special Close button behavior.\n<\/p>\n<p>\nNow things get interesting.\nRun the program and drag out of the client area (triggering\nthe drag\/drop behavior) and hover the mouse over the Close button.\n<\/p>\n<p>\nOw, my ears!\n<\/p>\n<p>\nWhat happened here?\n<\/p>\n<p>\nWhen the drag\/drop loop is in progress, the mouse is captured\nto the drag\/drop window.\nMouse capture means that all mouse messages go to that window\n(for as long as a mouse button is held down).\n&#8220;I don&#8217;t care what window you think the mouse is over; it&#8217;s over me!&#8221;\nAnother way of looking at this is that the capture window\nlogically covers the entire screen\n(for the purpose of determining who gets the mouse message).\n<\/p>\n<p>\nThe drag\/drop loop wants to know which window is under the drag cursor\nso it can figure out whose <i>IDropTarget<\/i> should receive\nthe drag\/drop notifications.\nThis <i>WindowFromPoint<\/i> call triggers a\n<code>WM_NC&shy;HIT&shy;TEST<\/code>\nmessage, which our program incorrectly interprets as a\n&#8220;the mouse is now in my window&#8221;.\n(Since the mouse is captured,\nthe mouse really isn&#8217;t in your window;\nit&#8217;s in the window that has capture because that window is stealing\nall the mouse input.)\nIt then performs its &#8220;The mouse is in the Close button&#8221;\nactivities (BEEP).\nBut since the mouse <i>was never in the window to begin with<\/i>,\nthe <code>Track&shy;Mouse&shy;Event<\/code> call that requests\n&#8220;let me know when the mouse leaves my window&#8221;\nposts a <code>WM&shy;_NC&shy;MOUSE&shy;LEAVE<\/code> message immediately.\nThe window then cleans up its &#8220;mouse is in the Close button&#8221;\nbehaviors, ready for the next cycle.\n<\/p>\n<p>\nAnd the next cycle begins pretty much as soon as the previous\ncycle finished,\nbecause the mouse\nis still physically (but not logically) in the Close button.\n<\/p>\n<p>\nResult: Infinite beep loop.\n<\/p>\n<p>\n(The real-life situation that triggered this article was much more\ncomplicated than this, involving an animation rather than a beep,\nbut the result was effectively the same:\nUnder the right circumstances, just moving the mouse over the caption\nresulted in the animation becoming an epileptic-seizure-inducing\nflicker as the animation continuously started and stopped.)\n<\/p>\n<p>\nAs we saw some time ago,\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2003\/10\/13\/55279.aspx\">\nthe <code>WM_MOUSE&shy;MOVE<\/code> message is the way to detect\nthat the mouse has entered your window<\/a>.\n(Though\nsome people haven&#8217;t figured this out and\n<a HREF=\"http:\/\/bytes.com\/topic\/visual-basic-net\/answers\/385246-code-wm_mouseenter\">\ncontinue on their\nfruitless quest for the <code>WM_MOUSE&shy;ENTER<\/code> message<\/a>.)\n<\/p>\n<p>\nIn our case, the applicable message is\n<code>WM_NC&shy;MOUSE&shy;MOVE<\/code>\nrather than <code>WM_MOUSE&shy;MOVE<\/code>,\nsince we are operating on the nonclient area.\nTherefore, the fix is to move the code that starts the animation from\n<code>WM_NC&shy;HIT&shy;TEST<\/code>\nto <code>WM_NC&shy;MOUSE&shy;MOVE<\/code>.\n<\/p>\n<pre>\n\/\/ Delete the old OnNcHitTest function and replace it with this\nvoid OnNcMouseMove(HWND hwnd, int x, int y, UINT codeHitTest)\n{\n FORWARD_WM_NCMOUSEMOVE(hwnd, x, y, codeHitTest, DefWindowProc);\n if (codeHitTest == HTCLOSE) {\n  EnterCloseButton(hwnd);\n } else {\n  LeaveCloseButton(hwnd);\n }\n return ht;\n}\n\/\/ delete HANDLE_MSG(hwnd, WM_NCHITTEST, OnNcHitTest);\nHANDLE_MSG(hwnd, WM_NCMOUSEMOVE, OnNcMouseMove);\n<\/pre>\n<p>\nRemember, if you want to do something when the mouse\nenters your window,\nwait until the mouse actually enters your window.\nThe <code>WM_NC&shy;HIT&shy;TEST<\/code> message doesn&#8217;t mean that\nthe mouse is in your window;\nit just means that somebody is asking,\n&#8220;If the mouse <i>were<\/i> in your window,\nwhat would it be doing?&#8221;\n<\/p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>The WM_NC&shy;HIT&shy;TEST message is sent to your window in order determine what part of the window corresponds to a particular point. The most common reason for this is that the mouse is over your window. The default WM_SET&shy;CURSOR handler uses the result of WM_NC&shy;HIT&shy;TEST to figure out what type of cursor to show. for example, [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-11453","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>The WM_NC&shy;HIT&shy;TEST message is sent to your window in order determine what part of the window corresponds to a particular point. The most common reason for this is that the mouse is over your window. The default WM_SET&shy;CURSOR handler uses the result of WM_NC&shy;HIT&shy;TEST to figure out what type of cursor to show. for example, [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/11453","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=11453"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/11453\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=11453"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=11453"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=11453"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}