{"id":104627,"date":"2020-12-31T07:00:00","date_gmt":"2020-12-31T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=104627"},"modified":"2020-12-31T07:30:10","modified_gmt":"2020-12-31T15:30:10","slug":"20201231-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20201231-00\/?p=104627","title":{"rendered":"How can I create a non-circular tab order, or some other type of custom ordering in my Win32 dialog?"},"content":{"rendered":"<p>Normally, the tab order in a dialog follows a fixed sequence: Hitting the <kbd>Tab<\/kbd> key moves forward through the sequence, and hitting <kbd>Shift<\/kbd>+<kbd>Tab<\/kbd> moves backward through the sequence, wrapping around when the beginning or end of the sequence is reached. In other words, what you have is a circle.<\/p>\n<p>The order is based on the order in which the controls are given in the dialog template, which need not match the physical layout of the controls. In other words, if you list a control near the bottom of the dialog ahead of a control near the top, then hitting the <kbd>Tab<\/kbd> key will move from the bottom control to the top control. This can be handy if you want the tab order to move vertically through columns, say.<\/p>\n<p>But sometimes a circular order isn&#8217;t good enough.<\/p>\n<p>Say you have a dialog box that looks in part like this:<\/p>\n<div style=\"border: solid 1px black; width: 25em; position: relative; padding: 1ex; background-color: white; color: black; font-family: Verdana, sans-serif; font-size: 10pt;\">\n<table style=\"background-color: white; color: black; font-family: Verdana, sans-serif; font-size: 10pt;\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td nowrap=\"nowrap\">Customer ID:\u00a0<\/td>\n<td style=\"border: solid 1px gray; width: 10em;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray; background-color: #e1e1e1; width: 6em;\" align=\"center\">Locate<\/td>\n<\/tr>\n<tr>\n<td style=\"height: .25em;\"><!-- empty - wordpress workaround --><\/td>\n<\/tr>\n<tr>\n<td>Name:\u00a0<\/td>\n<\/tr>\n<tr>\n<td>Address:\u00a0<\/td>\n<\/tr>\n<tr>\n<td>\u22ee<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray; background-color: #e1e1e1; width: 6em;\" align=\"center\">Change<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>The idea is that the user enters the customer ID into the edit box, and then clicks the <i>Locate<\/i> button. This looks up the customer record, and the user can then use other buttons on the dialog to view details of the customer or make changes.<\/p>\n<p>Based on end-user feedback, you come to the conclusion that it would be better if tabbing backward from the <i>Change<\/i> button went straight to the <i>Customer ID<\/i> field, rather than to the <i>Locate<\/i> button. After all, there&#8217;s no point clicking the <i>Locate<\/i> button without first making a change to the customer ID.<\/p>\n<p>You can do this by overriding the tab behavior for the <i>Change<\/i> button.<\/p>\n<pre>INT_PTR CALLBACK CustomerDlgProc(\r\n    HWND hdlg, UINT message, WPARAM wParam, LPARAM lParam)\r\n{\r\n  switch (message) {\r\n  case WM_INITDIALOG:\r\n    <span style=\"color: blue;\">SetWindowSubclass(GetDlgItem(hDlg, IDC_CHANGENAME),\r\n                      TabBackwardSubclassProc, 0, 0);<\/span>\r\n    ... other initialization ...\r\n    return TRUE;\r\n\r\n  case ...\r\n  }\r\n  return FALSE;\r\n}\r\n\r\nINT_PTR CALLBACK TabBackwardSubclassProc(\r\n    HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam,\r\n    UINT_PTR subclassId, DWORD_PTR)\r\n{\r\n  switch (message) {\r\n  case WM_NCDESTROY:\r\n    RemoveWindowSubclass(hwnd, TabBackwardSubclassProc,\r\n                         subclassId);\r\n    break;\r\n\r\n  <span style=\"color: blue;\">case WM_GETDLGCODE:\r\n    return DefSubclassProc(hwnd, message, wParam, lParam) |\r\n           DLGC_WANTTAB;\r\n\r\n  case WM_KEYDOWN:\r\n    if (wParam == VK_TAB) {\r\n      HWND hdlg = GetParent(hwnd);\r\n      if (GetKeyState(VK_SHIFT) &lt; 0) {\r\n        \/\/ Tabbing backward - go to the Customer ID.\r\n        HWND tabDestination = GetDlgItem(hdlg,\r\n                                         IDC_CUSTOMERID);\r\n        SendMessage(hdlg, WM_NEXTDLGCTL,\r\n                    (WPARAM)tabDestination, TRUE);\r\n      } else {\r\n        \/\/ Do the normal tabbing thing.\r\n        SendMessage(hdlg, WM_NEXTDLGCTL, FALSE, FALSE);\r\n      }\r\n      return 0;\r\n    }\r\n    break;\r\n\r\n    case WM_CHAR:\r\n      if (wParam == VK_TAB) return 0;\r\n      break;\r\n    }<\/span>\r\n\r\n    return DefSubclassProc(hwnd, message, wParam, lParam);\r\n}\r\n<\/pre>\n<p>During dialog box initialization, we subclass the control for which we want a custom tab destination. In our case, it&#8217;s the <i>Change<\/i> button.<\/p>\n<p>In the subclass procedure, there is the usual boilerplate about removing the subclass when the window is destroyed. But the interesting part starts with the <code>WM_<\/code><code>GET\u00adDLG\u00adCODE<\/code> message.<\/p>\n<p>As I noted some time ago, <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20031126-00\/?p=41703\"> the <code>WM_<\/code><code>GET\u00adDLG\u00adCODE<\/code> message lets you influence the behavior of the dialog manager<\/a>. We handle this message by taking the behavior requested by the original control, and also saying that we want to customize the behavior of the <kbd>Tab<\/kbd> key.<\/p>\n<p>Doing so allows the <code>VK_TAB<\/code> key to flow into the <code>WM_<\/code><code>KEY\u00adDOWN<\/code>, <code>WM_<\/code><code>KEY\u00adUP<\/code>, and <code>WM_<\/code><code>CHAR<\/code> messages.<\/p>\n<p>When a key goes down, we trigger our custom navigation when the <kbd>Tab<\/kbd> key is pressed. If the <kbd>Shift<\/kbd> key is also pressed, then we use the <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20040802-00\/?p=38283\"> <code>WM_<\/code><code>NEXT\u00adDLG\u00adCTL<\/code> message<\/a> to move focus to an explicit control: Passing an <code>lParam<\/code> of <code>TRUE<\/code> means that we are specifying the window to go to, and we give it the <i>Customer ID<\/i> control.<\/p>\n<p>If the <kbd>Shift<\/kbd> key is not pressed, then we pass an <code>lParam<\/code> of <code>FALSE<\/code>, meaning &#8220;Do default tab navigation.&#8221; Passing <code>FALSE<\/code> as the <code>wParam<\/code> means that we should go to the default <i>next<\/i> control. (Passing <code>TRUE<\/code> would request going to the default <i>previous<\/i> control.)<\/p>\n<p>The last bit of cleanliness is that we need to grab the <code>WM_<\/code><code>CHAR<\/code> message and swallow the <kbd>Tab<\/kbd> character, so that the control itself won&#8217;t try to respond to it, say, by inserting a tab into the edit control.<\/p>\n<p>And there you have it. We customized tabbing backward from the <i>Change<\/i> button in a way that resulted in a tab order that isn&#8217;t circular.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Taking things into your own hands.<\/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-104627","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Taking things into your own hands.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104627","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=104627"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104627\/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=104627"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=104627"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=104627"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}