{"id":8103,"date":"2012-03-14T07:00:00","date_gmt":"2012-03-14T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2012\/03\/14\/how-do-i-get-mouse-messages-faster-than-wm_mousemove\/"},"modified":"2012-03-14T07:00:00","modified_gmt":"2012-03-14T07:00:00","slug":"how-do-i-get-mouse-messages-faster-than-wm_mousemove","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20120314-00\/?p=8103","title":{"rendered":"How do I get mouse messages faster than WM_MOUSEMOVE?"},"content":{"rendered":"<p>We saw some time ago that\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2003\/10\/01\/55108.aspx\">\nthe rate at which you receive\n<code>WM_MOUSE&shy;MOVE<\/code> messages\nis entirely up to how fast your program calls\n<code>Get&shy;Message<\/code><\/a>.\nBut what if your program is calling\n<code>Get&shy;Message<\/code> as fast as it can,\nand it&#8217;s still not fast enough?\n<\/p>\n<p>\nYou can use\n<a HREF=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms646259(VS.85).aspx\">\nthe <code>Get&shy;Mouse&shy;Move&shy;Points&shy;Ex<\/code> function<\/a>\nto ask the window manager,\n&#8220;Hey, can you tell me about the mouse messages I missed?&#8221;\nI can think of two cases where you might want to do this:\n<\/p>\n<ul>\n<li>You are a program like Paint, where the user is drawing with the\n    mouse and you want to capture every nuance of the mouse motion.<\/p>\n<li>You are a program that supports something like mouse gestures,\n    so you want the full mouse curve information so you can do your\n    gesture recognition on it.\n<\/ul>\n<p>\nHere&#8217;s a program that I wrote for a relative of mine who is a radiologist.\nOne part of his job consists of sitting in a dark room\nstudying medical images.\nHe has to use his years of medical training to identify the tumor\n(<a HREF=\"https:\/\/www.youtube.com\/watch?v=OaTO8_KNcuo\">if there is one<\/a>),\nand then determine what percentage of the organ is afflicted.\nTo use this program, run it and position the circle so that\nit matches the location and size of the organ under study.\nOnce you have the circle positioned properly,\nuse the mouse to draw an outline of the tumor.\nWhen you let go of the mouse, the title bar will tell you the\nsize of the tumor relative to the entire organ.\n<\/p>\n<p>\n(Oh great, now I&#8217;m telling people to practice medicine without\na license.)\n<\/p>\n<p>\nFirst, we&#8217;ll do a version of the program that just calls\n<code>Get&shy;Message<\/code> as fast as it can.\nStart with the\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2005\/04\/22\/410773.aspx\">\nnew scratch program<\/a>\nand make the following changes:\n<\/p>\n<pre>\nclass RootWindow : public Window\n{\npublic:\n virtual LPCTSTR ClassName() { return TEXT(\"Scratch\"); }\n static RootWindow *Create();\nprotected:\n LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);\n <font COLOR=\"blue\">void PaintContent(PAINTSTRUCT *pps);\n BOOL WinRegisterClass(WNDCLASS *pwc);\nprivate:\n RootWindow();\n ~RootWindow();\n void OnCreate();\n void UpdateTitle();\n void OnSizeChanged(int cx, int cy);\n void AlwaysAddPoint(POINT pt);\n void AddPoint(POINT pt);\n void OnMouseMove(LPARAM lParam);\n void OnButtonDown(LPARAM lParam);\n void OnButtonUp(LPARAM lParam);\n \/\/ arbitrary limit (this is just a demo!)\n static const int cptMax = 1000;\nprivate:\n POINT  m_ptCenter;\n int    m_radius;\n BOOL   m_fDrawing;\n HPEN   m_hpenInside;\n HPEN   m_hpenDot;\n POINT  m_ptLast;\n int    m_cpt;\n POINT  m_rgpt[cptMax];\n};\nRootWindow::RootWindow()\n : m_fDrawing(FALSE)\n , m_hpenInside(CreatePen(PS_INSIDEFRAME, 3,\n                                  GetSysColor(COLOR_WINDOWTEXT)))\n , m_hpenDot(CreatePen(PS_DOT, 1, GetSysColor(COLOR_WINDOWTEXT)))\n{\n}\nRootWindow::~RootWindow()\n{\n if (m_hpenInside) DeleteObject(m_hpenInside);\n if (m_hpenDot) DeleteObject(m_hpenDot);\n}\nBOOL RootWindow::WinRegisterClass(WNDCLASS *pwc)\n{\n pwc-&gt;style |= CS_VREDRAW | CS_HREDRAW;\n return __super::WinRegisterClass(pwc);\n}\nvoid RootWindow::OnCreate()\n{\n SetLayeredWindowAttributes(m_hwnd, 0, 0xA0, LWA_ALPHA);\n}\nvoid RootWindow::UpdateTitle()\n{\n TCHAR szBuf[256];\n \/\/ Compute the area of the circle using a surprisingly good\n \/\/ rational approximation to <a HREF=\"http:\/\/www.piday.org\/\">pi<\/a>.\n int circleArea = m_radius * m_radius * 355 \/ 113;\n \/\/ Compute the area of the region, if we have one\n if (m_cpt &gt; 0 &amp;&amp; !m_fDrawing) {\n  int polyArea = 0;\n  for (int i = 1; i &lt; m_cpt; i++) {\n   polyArea += m_rgpt[i-1].x * m_rgpt[i  ].y -\n               m_rgpt[i  ].x * m_rgpt[i-1].y;\n  }\n  if (polyArea &lt; 0) polyArea = -polyArea; \/\/ ignore orientation\n  polyArea \/= 2;\n  wnsprintf(szBuf, 256,\n           TEXT(\"circle area is %d, poly area is %d = %d%%\"),\n           circleArea, polyArea,\n           MulDiv(polyArea, 100, circleArea));\n } else {\n  wnsprintf(szBuf, 256, TEXT(\"circle area is %d\"), circleArea);\n }\n SetWindowText(m_hwnd, szBuf);\n}\nvoid RootWindow::OnSizeChanged(int cx, int cy)\n{\n m_ptCenter.x = cx \/ 2;\n m_ptCenter.y = cy \/ 2;\n m_radius = min(m_ptCenter.x, m_ptCenter.y) - 6;\n if (m_radius &lt; 0) m_radius = 0;\n UpdateTitle();\n}\nvoid RootWindow::PaintContent(PAINTSTRUCT *pps)\n{\n HBRUSH hbrPrev = SelectBrush(pps-&gt;hdc,\n                                    GetStockBrush(<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2004\/01\/26\/62991.aspx\">HOLLOW_BRUSH<\/a>));\n HPEN hpenPrev = SelectPen(pps-&gt;hdc, m_hpenInside);\n Ellipse(pps-&gt;hdc, m_ptCenter.x - m_radius,\n                   m_ptCenter.y - m_radius,\n                   m_ptCenter.x + m_radius,\n                   m_ptCenter.y + m_radius);\n SelectPen(pps-&gt;hdc, m_hpenDot);\n Polyline(pps-&gt;hdc, m_rgpt, m_cpt);\n SelectPen(pps-&gt;hdc, hpenPrev);\n SelectBrush(pps-&gt;hdc, hbrPrev);\n}\nvoid RootWindow::AddPoint(POINT pt)\n{\n \/\/ Ignore duplicates\n if (pt.x == m_ptLast.x &amp;&amp; pt.y == m_ptLast.y) return;\n \/\/ Stop if no room for more\n if (m_cpt &gt;= cptMax) return;\n AlwaysAddPoint(pt);\n}\nvoid RootWindow::AlwaysAddPoint(POINT pt)\n{\n \/\/ Overwrite the last point if we can't add a new one\n if (m_cpt &gt;= cptMax) m_cpt = cptMax - 1;\n \/\/ Invalidate the rectangle connecting this point\n \/\/ to the last point\n RECT rc = { pt.x, pt.y, pt.x+1, pt.y+1 };\n if (m_cpt &gt; 0) {\n  RECT rcLast = { m_ptLast.x,   m_ptLast.y,\n                  m_ptLast.x+1, m_ptLast.y+1 };\n  UnionRect(&amp;rc, &amp;rc, &amp;rcLast);\n }\n InvalidateRect(m_hwnd, &amp;rc, FALSE);\n \/\/ Add the point\n m_rgpt[m_cpt++] = pt;\n m_ptLast = pt;\n}\nvoid RootWindow::OnMouseMove(LPARAM lParam)\n{\n if (m_fDrawing) {\n  POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };\n  AddPoint(pt);\n }\n}\nvoid RootWindow::OnButtonDown(LPARAM lParam)\n{\n \/\/ Erase any previous polygon\n InvalidateRect(m_hwnd, NULL, TRUE);\n m_cpt = 0;\n POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };\n AlwaysAddPoint(pt);\n m_fDrawing = TRUE;\n}\nvoid RootWindow::OnButtonUp(LPARAM lParam)\n{\n if (!m_fDrawing) return;\n OnMouseMove(lParam);\n \/\/ Close the loop, eating the last point if necessary\n AlwaysAddPoint(m_rgpt[0]);\n m_fDrawing = FALSE;\n UpdateTitle();\n}<\/font>\nLRESULT RootWindow::HandleMessage(\n                          UINT uMsg, WPARAM wParam, LPARAM lParam)\n{\n switch (uMsg) {\n  case WM_CREATE:\n   <font COLOR=\"blue\">OnCreate();\n   break;<\/font>\n  case WM_NCDESTROY:\n   \/\/ Death of the root window ends the thread\n   PostQuitMessage(0);\n   break;\n  case WM_SIZE:\n   <font COLOR=\"blue\">if (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED) {\n    OnSizeChanged(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));\n   }\n   break;\n  case WM_MOUSEMOVE:\n   OnMouseMove(lParam);\n   break;\n  case WM_LBUTTONDOWN:\n   OnButtonDown(lParam);\n   break;\n  case WM_LBUTTONUP:\n   OnButtonUp(lParam);\n   break;<\/font>\n }\n return __super::HandleMessage(uMsg, wParam, lParam);\n}\nRootWindow *RootWindow::Create()\n{\n RootWindow *self = new(std::nothrow) RootWindow();\n if (self &amp;&amp; self-&gt;WinCreateWindow(<font COLOR=\"blue\">WS_EX_LAYERED<\/font>,\n       TEXT(\"Scratch\"), WS_OVERLAPPEDWINDOW,\n       CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,\n       NULL, NULL)) {\n      return self;\n  }\n delete self;\n return NULL;\n}\n<\/pre>\n<p>\nThis program records every mouse movement while the button\nis down and replays them in the form of a dotted polygon.\nWhen the mouse button goes up, it calculates the area both\nin terms of pixels and in terms of a percentage of the circle.\n<\/p>\n<p>\nThis program works well.\nMy relative&#8217;s hand moves slowly enough (after all, it has\nto trace a tumor) that the <code>Get&shy;Message<\/code> loop\nis plenty fast enough to keep up.\nBut just for the sake of illustration, suppose it isn&#8217;t.\nTo make the effect easier to see, let&#8217;s add some artificial\ndelays:\n<\/p>\n<pre>\nvoid RootWindow::OnMouseMove(LPARAM lParam)\n{\n if (m_fDrawing) {\n  POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };\n  AddPoint(pt);\n  <font COLOR=\"blue\">UpdateWindow(m_hwnd);\n  Sleep(100);<\/font>\n }\n}\n<\/pre>\n<p>\nNow, if you try to draw with the mouse, you see all sorts of\njagged edges because our program can&#8217;t keep up.\n(The <code>Update&shy;Window<\/code> is just to make the most recent\nline visible while we are sleeping.)\n<\/p>\n<p>\nEnter\n<code>Get&shy;Mouse&shy;Move&shy;Points&shy;Ex<\/code>.\nThis gives you all the mouse activity that led up to a specific\npoint in time,\nallowing you to fill in the data that you missed because you weren&#8217;t\npumping messages fast enough.\nLet&#8217;s teach our program how to take advantage of this:\n<\/p>\n<pre>\nclass RootWindow : public Window\n{\n...\n void AlwaysAddPoint(POINT pt);\n <font COLOR=\"blue\">void AddMissingPoints(POINT pt, DWORD tm);<\/font>\n void AddPoint(POINT pt);\n...\n POINT m_ptLast;\n <font COLOR=\"blue\">DWORD m_tmLast;<\/font>\n int   m_cpt;\n};\n<font COLOR=\"blue\">void RootWindow::AddMissingPoints(POINT pt, DWORD tm)\n{\n \/\/ See discussion for why this code is wrong\n <i>ClientToScreen(m_hwnd, &amp;pt);<\/i>\n MOUSEMOVEPOINT mmpt = { pt.x, pt.y, tm };\n MOUSEMOVEPOINT rgmmpt[64];\n int cmmpt = GetMouseMovePointsEx(sizeof(mmpt), &amp;mmpt,\n                            rgmmpt, 64, GMMP_USE_DISPLAY_POINTS);\n POINT ptLastScreen = m_ptLast;\n <i>ClientToScreen(m_hwnd, &amp;ptLastScreen);<\/i>\n int i;\n for (i = 0; i &lt; cmmpt; i++) {\n  if (rgmmpt[i].time &lt; m_tmLast) break;\n  if (rgmmpt[i].time == m_tmLast &amp;&amp;\n      rgmmpt[i].x == ptLastScreen.x &amp;&amp;\n      rgmmpt[i].y == ptLastScreen.y) break;\n }\n while (--i &gt;= 0) {\n   POINT ptClient = { rgmmpt[i].x, rgmmpt[i].y };\n   <i>ScreenToClient(m_hwnd, &amp;ptClient);<\/i>\n   AddPoint(ptClient);\n }\n}<\/font>\nvoid RootWindow::AlwaysAddPoint(POINT pt)\n{\n...\n \/\/ Add the point\n m_rgpt[m_cpt++] = pt;\n m_ptLast = pt;\n <font COLOR=\"blue\">m_tmLast = GetMessageTime();<\/font>\n}\nvoid RootWindow::OnMouseMove(LPARAM lParam)\n{\n if (m_fDrawing) {\n  POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };\n  <font COLOR=\"blue\">AddMissingPoints(pt, GetMessageTime());<\/font>\n  AddPoint(pt);\n  UpdateWindow(m_hwnd);\n  Sleep(100); \/\/ artificial delay to simulate unresponsive app\n }\n}\n<\/pre>\n<p>\nBefore updating the the current mouse position,\nwe check to see if there were other mouse motions\nthat occurred while we weren&#8217;t paying attention.\nWe tell <code>Get&shy;Mouse&shy;Move&shy;Points&shy;Ex<\/code>,\n&#8220;Hey, here is a mouse message that I have right now.\nPlease tell me about the stuff that I missed.&#8221;\nIt fills in an array with recent mouse history,\nmost recent events first.\nWe go through that array looking for the previous point,\nand give up either when we find it, or when the timestamps\non the events we received take us too far backward in time.\nOnce we find all the points that we missed,\nwe play them into the <code>Add&shy;Point<\/code> function.\n<\/p>\n<p>\n<b>Notes to people who like to copy code without understanding it<\/b>:\nThe code fragment above works only for\nsingle-monitor systems.\nTo work correctly on multiple-monitor systems,\nyou need to include the crazy coordinate-shifting\ncode provided in the documentation for\n<code>Get&shy;Mouse&shy;Move&shy;Points&shy;Ex<\/code>.\n(I omitted that code because it would just be distracting.)\nAlso, the management of <code>m_tmLast<\/code> is now\nrather confusing, but I did it this way to minimize the\namount of change to the original program.\nIt would probably be better to have added a\n<code>DWORD tm<\/code> parameter to <code>Add&shy;Point<\/code>\ninstead of trying to infer it from the current message time.\n<\/p>\n<p>\nThe\n<code>Get&shy;Mouse&shy;Move&shy;Points&shy;Ex<\/code>\ntechnique is also\nhandy if you need to refer back to the historical record.\nFor example, if the user dragged the mouse out of your window\nand you want to calculate the velocity with which the mouse exited,\nyou can use\n<code>Get&shy;Mouse&shy;Move&shy;Points&shy;Ex<\/code>\nto get the most\nrecent mouse activity and calculate the velocity.\nThis saves you from having to record all the mouse activity yourself\non the off chance that the mouse might leave the window.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We saw some time ago that the rate at which you receive WM_MOUSE&shy;MOVE messages is entirely up to how fast your program calls Get&shy;Message. But what if your program is calling Get&shy;Message as fast as it can, and it&#8217;s still not fast enough? You can use the Get&shy;Mouse&shy;Move&shy;Points&shy;Ex function to ask the window manager, &#8220;Hey, [&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-8103","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>We saw some time ago that the rate at which you receive WM_MOUSE&shy;MOVE messages is entirely up to how fast your program calls Get&shy;Message. But what if your program is calling Get&shy;Message as fast as it can, and it&#8217;s still not fast enough? You can use the Get&shy;Mouse&shy;Move&shy;Points&shy;Ex function to ask the window manager, &#8220;Hey, [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/8103","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=8103"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/8103\/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=8103"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=8103"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=8103"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}