{"id":97495,"date":"2017-11-30T07:00:00","date_gmt":"2017-11-30T22:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/?p=97495"},"modified":"2019-03-13T01:20:42","modified_gmt":"2019-03-13T08:20:42","slug":"20171130-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20171130-00\/?p=97495","title":{"rendered":"Creating tree view check boxes manually: Responding to clicks"},"content":{"rendered":"<p><a HREF=\"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/20171129-00\/?p=97485\">Last time<\/a>, we added state images to the tree view, but we didn&#8217;t provide any way for the user to click on them. Let&#8217;s add the code so that the user can click on the check box to change the value. For simplicity, we will just cycle through the state images. In real life, you would probably have more complex logic for deciding what to do when the user clicks on the check box, such as ignoring clicks on disabled check boxes. <\/p>\n<p>Take our program from last time and make these additions: <\/p>\n<pre>\nvoid CycleStateImage(HWND hwndTV, HTREEITEM hti)\n{\n  UINT oldState = TreeView_GetItemState(hwndTV, hti,\n                                        TVIS_STATEIMAGEMASK);\n  int stateIndex = (int)(oldState &amp; TVIS_STATEIMAGEMASK) &gt;&gt; 12;\n\n  stateIndex = stateIndex + 1;\n  if (stateIndex &gt;= ImageList_GetImageCount(\n                TreeView_GetImageList(hwndTV, TVSIL_STATE)))\n  {\n    \/\/ We ran out of states. Wrap around to the first state.\n    stateIndex = 1;\n  }\n\n  TreeView_SetItemState(hwndTV, hti,\n      INDEXTOSTATEIMAGEMASK(stateIndex), TVIS_STATEIMAGEMASK);\n}\n<\/pre>\n<p>To cycle to the next state image, we request the state image mask from the current state, isolate the state image mask, and increment it, wrapping around to 1 if we were on the last state. (Note that we wrap around to 1 rather than to 0, because we saw last time that state image zero is not used; if you ask for state image zero, that means that you want no state image at all.) <\/p>\n<pre>\nLRESULT OnNotifyTVClicked(HWND hwndTV)\n{\n  TVHITTESTINFO tvhti;\n  DWORD dwPos = GetMessagePos();\n  tvhti.pt.x = GET_X_LPARAM(dwPos);\n  tvhti.pt.y = GET_Y_LPARAM(dwPos);\n  MapWindowPoints(HWND_DESKTOP, hwndTV, &amp;tvhti.pt, 1);\n  HTREEITEM hti = TreeView_HitTest(hwndTV, &amp;tvhti);\n  if (tvhti.flags &amp; TVHT_ONITEMSTATEICON)\n  {\n    CycleStateImage(hwndTV, hti);\n    return TRUE; \/\/ handled\n  }\n  return FALSE; \/\/ not handled\n}\n<\/pre>\n<p>Frustratingly, the tree view control notifies us when it receives a click, but it doesn&#8217;t tell us where the click was. We have to fetch it ourselves by calling <code>Get&shy;Message&shy;Pos()<\/code>, and then converting screen coordinates to client coordinates. Once we have those coordinates, we ask the tree view what is at those coordinates, and if it says &#8220;Oh, no big deal, just the state image&sup1; for an item,&#8221; then we get all excited and cycle the state image. <\/p>\n<pre>\nLRESULT OnNotifyTVKeyDown(HWND hwndTV, NMTVKEYDOWN* ptvkd)\n{\n  switch (ptvkd-&gt;wVKey)\n  {\n  case VK_SPACE:\n    HTREEITEM hti = TreeView_GetNextItem(hwndTV, nullptr,\n                                         TVGN_CARET);\n    if (hti)\n    {\n      CycleStateImage(hwndTV, hti);\n      return TRUE; \/\/ handled\n    }\n  }\n  return FALSE; \/\/ not handled\n}\n<\/pre>\n<p>For keyboard accessibility, we use the space bar as an equivalent to clicking on the state image. <\/p>\n<pre>\nLRESULT OnNotify(HWND hwnd, int idFrom, NMHDR* pnm)\n{\n  if (pnm-&gt;hwndFrom == g_hwndChild)\n  {\n    switch (pnm-&gt;code)\n    {\n    case NM_CLICK:\n      return OnNotifyTVClicked(pnm-&gt;hwndFrom);\n\n    case TVN_KEYDOWN:\n      return OnNotifyTVKeyDown(pnm-&gt;hwndFrom,\n         CONTAINING_RECORD(pnm, NMTVKEYDOWN, hdr));\n    }\n  }\n  return 0; \/\/ unhandled\n}\n\n\/\/ Add to WndProc\n  HANDLE_MSG(hwnd, WM_NOTIFY, OnNotify);\n<\/pre>\n<p>Here is our <code>WM_<\/code><code>NOTIFY<\/code> handler. If the notification is coming from the tree view control, then dispatch click notifications to <code>On&shy;Notify&shy;TV&shy;Clicked<\/code> and dispatch key-down notifications to <code>On&shy;Notify&shy;TV&shy;Key&shy;Down<\/code>. The <code>TVN_<\/code><code>KEY&shy;DOWN<\/code> notification comes with a custom notification structure, so we convert our generic <code>NMHDR<\/code> pointer to a <code>NMTV&shy;KEY&shy;DOWN<\/code> pointer. <\/p>\n<p>And there we have it. We manually implemented tree view check boxes. This code is effectively equivalent&sup2; to what you get when you turn on <code>TVS_<\/code><code>CHECK&shy;BOXES<\/code> and it&#8217;s this code that was moved into the tree view control. <\/p>\n<p>Next time, we&#8217;ll bring tree view check boxes into the 21st century. <\/p>\n<p>&sup1; Note the inconsistent terminology. Normally, the state image is called a &#8220;state image&#8221;, but here, it&#8217;s called a &#8220;state icon&#8221;. <\/p>\n<p>&sup2; Plus a few more quirks we&#8217;ll learn about later. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>Cycling through the state images.<\/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-97495","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Cycling through the state images.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/97495","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=97495"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/97495\/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=97495"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=97495"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=97495"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}