{"id":100035,"date":"2018-10-23T07:00:00","date_gmt":"2018-10-23T21:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/?p=100035"},"modified":"2019-03-13T00:22:28","modified_gmt":"2019-03-13T07:22:28","slug":"20181023-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20181023-00\/?p=100035","title":{"rendered":"Adding a Ctrl+arrow accelerator for moving the trackbar by just one unit, part 1: Initial plunge"},"content":{"rendered":"<p>When you create a <a HREF=\"https:\/\/msdn.microsoft.com\/en-us\/library\/windows\/desktop\/bb760149(v=vs.85).aspx\">trackbar common control<\/a>, you can specify how far the arrow keys will change the trackbar position (default: 1 unit) and how far the <kbd>PgUp<\/kbd> and <kbd>PgDn<\/kbd> keys will change the trackbar position (default: one fifth of the range). <\/p>\n<p>If you change the default distance for the arrow keys to, say, five units, then you probably want to add a keyboard accelerator for moving by just one units, so that somebody can use <code>PgUp<\/code> and <code>PgDn<\/code> to get in the general area they want to be, then the arrow keys to get close, and then finally the <kbd>Ctrl<\/kbd>+arrow keys to get the exact value. <\/p>\n<p>Take <a HREF=\"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/20030723-00\/?p=43073\">the scratch program<\/a> and make the following changes: <\/p>\n<pre>\n<font COLOR=\"blue\">#include &lt;strsafe.h&gt;<\/font>\n\nBOOL\nOnCreate(HWND hwnd, LPCREATESTRUCT lpcs)\n{\n <font COLOR=\"blue\">g_hwndChild = CreateWindow(TRACKBAR_CLASS, TEXT(\"\"),\n    WS_CHILD | WS_VISIBLE,\n    0, 0, 100, 100,\n    hwnd, (HMENU)100, g_hinst, 0);\n\n  SendMessage(g_hwndChild, TBM_SETLINESIZE, 0, 5);\n  SendMessage(g_hwndChild, TBM_SETPAGESIZE, 0, 20);<\/font>\n\n  return TRUE;\n}\n<\/pre>\n<p>We start by creating a trackbar control and setting the line size to 5 units and page size to 20 units. <\/p>\n<pre>\n<font COLOR=\"blue\">void OnHScroll(HWND hwnd, HWND hwndCtl, UINT code, int pos)\n{\n if (hwndCtl == g_hwndChild) {\n  TCHAR buf[128];\n  pos = (int)SendMessage(hwndCtl, TBM_GETPOS, 0, 0);\n  StringCchPrintf(buf, ARRAYSIZE(buf), TEXT(\"pos = %d\"), pos);\n  SetWindowText(hwnd, buf);\n }\n}\n\n    HANDLE_MSG(hwnd, WM_HSCROLL, OnHScroll);<\/font>\n<\/pre>\n<p>And we respond to the <code>WM_<\/code><code>HSCROLL<\/code> message by displaying the trackbar&#8217;s new position. <\/p>\n<p>If you run this program, you&#8217;ll see a happy trackbar, and you can use the keyboard to move the thumb by 20 units (with <code>PgUp<\/code> and <code>PgDn<\/code>), or by 5 units (with the left and right arrow keys). But there&#8217;s no way to move the thumb by just one unit. <\/p>\n<p>Let&#8217;s fix that. <\/p>\n<p>But how? <\/p>\n<p>The first thing that comes to mind is to subclass the trackbar control and add a new keyboard accelerator. So let&#8217;s do that. <\/p>\n<pre>\n<font COLOR=\"blue\">LRESULT CALLBACK TrackbarKeyProc(\n    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam,\n    UINT_PTR uIdSubclass, DWORD_PTR dwRefData)\n{\n if (uMsg == WM_KEYDOWN &amp;&amp; GetKeyState(VK_CONTROL) &lt; 0) {\n  int delta = 0;\n  if (wParam == VK_LEFT) {\n   delta = -1;\n  } else if (wParam == VK_RIGHT) {\n   delta = +1;\n  }\n\n  if (delta) {\n   auto pos = SendMessage(hwnd, TBM_GETPOS, 0, 0);\n   pos += delta;\n   SendMessage(hwnd, TBM_SETPOS, TRUE, pos);\n   FORWARD_WM_HSCROLL(GetParent(hwnd), hwnd,\n    delta &lt; 0 ? TB_LINEUP : TB_LINEDOWN, 0, SendMessage);\n   return 0;\n  }\n }\n return DefSubclassProc(hwnd, uMsg, wParam, lParam);\n}<\/font>\n\nBOOL\nOnCreate(HWND hwnd, LPCREATESTRUCT lpcs)\n{\n g_hwndChild = CreateWindow(TRACKBAR_CLASS, TEXT(\"\"),\n    WS_CHILD | WS_VISIBLE, 0, 0, 100, 100,\n    hwnd, (HMENU)100, g_hinst, 0);\n\n  SendMessage(g_hwndChild, TBM_SETLINESIZE, 0, 5);\n  SendMessage(g_hwndChild, TBM_SETPAGESIZE, 0, 20);\n\n  <font COLOR=\"blue\">SetWindowSubclass(g_hwndChild, TrackbarKeyProc, 0, 0);<\/font>\n  return TRUE;\n}\n\nvoid\nOnDestroy(HWND hwnd)\n{\n  <font COLOR=\"blue\">RemoveWindowSubclass(g_hwndChild, TrackbarKeyProc, 0);<\/font>\n  PostQuitMessage(0);\n}\n<\/pre>\n<p>With this version, you can hold the <kbd>Ctrl<\/kbd> key when pressing the left or right arrow keys, and the position will change by just one unit. <\/p>\n<p>Mission accomplished? <\/p>\n<p>Not quite. There&#8217;s still a lot of stuff missing. You may not notice it right away, but you will eventually, probably when one of your customers reports a problem that makes you have to scramble a fix. <\/p>\n<p>For example, this code doesn&#8217;t manage keyboard focus indicators. Let&#8217;s fix that. <\/p>\n<pre>\nLRESULT CALLBACK TrackbarKeyProc(\n    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam,\n    UINT_PTR uIdSubclass, DWORD_PTR dwRefData)\n{\n if (uMsg == WM_KEYDOWN &amp;&amp; GetKeyState(VK_CONTROL) &lt; 0) {\n  ...\n  if (delta) {\n   ...\n   FORWARD_WM_HSCROLL(GetParent(hwnd), hwnd,\n    delta &lt; 0 ? TB_LINEUP : TB_LINEDOWN, 0, SendMessage);\n   <font COLOR=\"blue\">SendMessage(hwnd, WM_CHANGEUISTATE,\n    MAKELONG(UIS_CLEAR, UISF_HIDEFOCUS), 0);<\/font>\n   return 0;\n  }\n }\n return DefSubclassProc(hwnd, uMsg, wParam, lParam);\n}\n<\/pre>\n<p>Next, this version doesn&#8217;t support vertical trackbars at all. If you add the <code>TBS_<\/code><code>VERT<\/code> style to the <code>Create&shy;Window<\/code> call, you&#8217;ll have a vertical scroll bar, and we haven&#8217;t been doing anything with the up and down arrows. <\/p>\n<p>In related news, the trackbar allows you to use the up and down arrows to change the position of horizontal scroll bars. The up arrow behaves like the right arrow, and the down arrow behaves like the left arrow. Maybe you have customers who rely on this behavior, say, because that&#8217;s what their accessibility tool uses. <\/p>\n<p>Fortunately, one set of changes covers both of these issues. <\/p>\n<pre>\nLRESULT CALLBACK TrackbarKeyProc(\n    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam,\n    UINT_PTR uIdSubclass, DWORD_PTR dwRefData)\n{\n if (uMsg == WM_KEYDOWN &amp;&amp; GetKeyState(VK_CONTROL) &lt; 0) {\n  int delta = 0;\n  if (wParam == VK_LEFT <font COLOR=\"blue\">|| wParam == VK_UP<\/font>) {\n   delta = -1;\n  } else if (wParam == VK_RIGHT <font COLOR=\"blue\">|| wParam == VK_DOWN<\/font>) {\n   delta = +1;\n  }\n  ...\n }\n return DefSubclassProc(hwnd, uMsg, wParam, lParam);\n}\n<\/pre>\n<p>But wait, there&#8217;s also the <code>TBS_<\/code><code>DOWN&shy;IS&shy;LEFT<\/code> style that changes the mapping between vertical and horizontal. If the style is set, then the up arrow acts like the left arrow, and the down arrow acts like the right arrow. <\/p>\n<p>Okay, so let&#8217;s fix that too. <\/p>\n<pre>\n<font COLOR=\"blue\">WPARAM SwapKeys(WPARAM wParam, UINT vk1, UINT vk2)\n{\n  if (wParam == vk1) return vk2;\n  if (wParam == vk2) return vk1;\n  return wParam;\n}<\/font>\n\nLRESULT CALLBACK TrackbarKeyProc(\n    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam,\n    UINT_PTR uIdSubclass, DWORD_PTR dwRefData)\n{\n if (uMsg == WM_KEYDOWN &amp;&amp; GetKeyState(VK_CONTROL) &lt; 0) {\n  <font COLOR=\"blue\">DWORD style = GetWindowStyle(hwnd);\n  if (style &amp; TBS_DOWNISLEFT) {\n   if (style &amp; TBS_VERT) {\n    wParam = SwapKeys(wParam, VK_LEFT, VK_RIGHT);\n   } else {\n    wParam = SwapKeys(wParam, VK_UP, VK_DOWN);\n   }\n  }<\/font>\n\n  int delta = 0;\n  if (wParam == VK_LEFT || wParam == VK_UP) {\n   delta = -1;\n  } else if (wParam == VK_RIGHT || wParam == VK_DOWN) {\n   delta = +1;\n  }\n\n  if (delta) {\n   auto pos = SendMessage(hwnd, TBM_GETPOS, 0, 0);\n   pos += delta;\n   SendMessage(hwnd, TBM_SETPOS, TRUE, pos);\n   FORWARD_WM_HSCROLL(GetParent(hwnd), hwnd,\n    delta &lt; 0 ? TB_LINEUP : TB_LINEDOWN, 0, SendMessage);\n   SendMessage(hwnd, WM_CHANGEUISTATE,\n    MAKELONG(UIS_CLEAR, UISF_HIDEFOCUS), 0);\n   return 0;\n  }\n }\n return DefSubclassProc(hwnd, uMsg, wParam, lParam);\n}\n<\/pre>\n<p>Okay, are we done now? <\/p>\n<p>Nope, you still have right-to-left languages to deal with. In those cases, we want to flip the meanings of the left and right arrows. <\/p>\n<pre>\nLRESULT CALLBACK TrackbarKeyProc(\n    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam,\n    UINT_PTR uIdSubclass, DWORD_PTR dwRefData)\n{\n if (uMsg == WM_KEYDOWN &amp;&amp; GetKeyState(VK_CONTROL) &lt; 0) {\n  <font COLOR=\"blue\">if (GetWindowExStyle(hwnd) &amp; WS_EX_LAYOUTRTL) {\n   wParam = SwapKeys(wParam, VK_LEFT, VK_RIGHT);\n  }<\/font>\n\n  DWORD style = GetWindowStyle(hwnd);\n  if (style &amp; TBS_DOWNISLEFT) {\n   if (style &amp; TBS_VERT) {\n    wParam = SwapKeys(wParam, VK_LEFT, VK_RIGHT);\n   } else {\n    wParam = SwapKeys(wParam, VK_UP, VK_DOWN);\n   }\n  }\n\n  int delta = 0;\n  if (wParam == VK_LEFT || wParam == VK_UP) {\n   delta = -1;\n  } else if (wParam == VK_RIGHT || wParam == VK_DOWN) {\n   delta = +1;\n  }\n\n  if (delta) {\n   auto pos = SendMessage(hwnd, TBM_GETPOS, 0, 0);\n   pos += delta;\n   SendMessage(hwnd, TBM_SETPOS, TRUE, pos);\n   FORWARD_WM_HSCROLL(GetParent(hwnd), hwnd,\n    delta &lt; 0 ? TB_LINEUP : TB_LINEDOWN, 0, SendMessage);\n   SendMessage(hwnd, WM_CHANGEUISTATE,\n    MAKELONG(UIS_CLEAR, UISF_HIDEFOCUS), 0);\n   return 0;\n  }\n }\n return DefSubclassProc(hwnd, uMsg, wParam, lParam);\n}\n<\/pre>\n<p>Okay, <i>now<\/i> are we done? <\/p>\n<p>Maybe. I think that covers the remaining issues, but maybe I missed something. <\/p>\n<p>Y&#8217;know, this started out as a simple fix, but all the special cases turned it into a complicated mess. And maybe a future version of the trackbar control will add yet another style that introduces another special case. What we really want to do is hook into the control after it has decided what to do with the keyboard and before it changes the trackbar position. <\/p>\n<p>Let&#8217;s work on that next time. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>The obvious thing starts to get out of hand.<\/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-100035","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>The obvious thing starts to get out of hand.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/100035","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=100035"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/100035\/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=100035"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=100035"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=100035"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}