{"id":111976,"date":"2026-01-09T07:00:00","date_gmt":"2026-01-09T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111976"},"modified":"2026-01-09T08:27:30","modified_gmt":"2026-01-09T16:27:30","slug":"20260109-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260109-00\/?p=111976","title":{"rendered":"Using Active Accessibility to find out where the focus item is"},"content":{"rendered":"<p>Last time, <a title=\"Using Active Accessibility to find out where the Windows caret is\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260108-00\/?p=111973\"> we learned how to use Active Accessibility to find the caret<\/a>, but what if the focus is not on an edit control? In that case, we want to find the focus object.<\/p>\n<pre>GUITHREADINFO info = { sizeof(GUITHREADINFO) };\r\nif (GetGUIThreadInfo(0, &amp;info))\r\n{\r\n    if (info.flags &amp; GUI_CARETBLINKING) {\r\n        \u27e6 ... \u27e7\r\n    }\r\n    if (info.hwndFocus != nullptr) {\r\n        wil::com_ptr_nothrow&lt;IAccessible&gt; acc;\r\n        if (SUCCEEDED(AccessibleObjectFromWindow(info.hwndFocus, OBJID_CARET,\r\n                             IID_PPV_ARGS(acc.put()))) &amp;&amp; acc) {\r\n            \u27e6 ... \u27e7\r\n        }\r\n        <span style=\"border: solid 1px currentcolor; border-bottom: none;\">if (SUCCEEDED(AccessibleObjectFromWindow(info.hwndFocus, OBJID_CLIENT,<\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">                     IID_PPV_ARGS(acc.put()))) &amp;&amp; acc) {              <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    wil::unique_variant vt;                                           <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    if (acc-&gt;get_accFocus(&amp;vt) == S_OK) {                             <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">        ... to be written ...                                         <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    }                                                                 <\/span>\r\n        <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                                                     <\/span>\r\n    }\r\n}\r\n<\/pre>\n<p>If we cannot get the location of the system caret, and the focus window denies that it has any caret at all (not even a custom one), then we ask the focus window for the object that represents the window itself (<code>OBJID_<wbr \/>CLIENT<\/code>), and then ask that object for an identifier for the currently-focused child. We use a <code>wil::<wbr \/>unique_<wbr \/>variant<\/code> to ensure that the variant is cleaned up with <code>Variant\u00adClear()<\/code>, thereby avoiding a leak if the variant type requires cleanup.<\/p>\n<p>Getting the location of the focused child from the child identifier requires a different algorithm depending on the form of the identifier, as documented in &#8220;<a title=\"How Child IDs Are Used in Parameters: Output Parameters\" href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/win32\/winauto\/how-child-ids-are-used-in-parameters#output-parameters\">How child IDs are used in output parameters<\/a>&#8220;.<\/p>\n<ul>\n<li>If the identifier is in the form of a <code>VT_DISPATCH<\/code>, then convert the <code>pdispVal<\/code> to <code>IAccessible<\/code> and use <code>CHILDID_SELF<\/code> as the child ID.<\/li>\n<li>If the identifier is in the form of a <code>VT_I4<\/code>, then use <code>get_accChild<\/code> with the <code>lVal<\/code> to get an <code>IDispatch<\/code>, which you can then convert to <code>IAccessible<\/code>, and use <code>CHILDID_SELF<\/code> as the child ID.<\/li>\n<li>If the identifier is in the form of a <code>VT_I4<\/code>, but <code>get_accChild<\/code> fails, then use the original object and the <code>lVal<\/code> as the child ID.<\/li>\n<\/ul>\n<p>There&#8217;s also a small optimization: If the identifier is in the form of a <code>VT_I4<\/code> and it is already <code>CHILDID_SELF<\/code>, then use the original object with <code>CHILDID_SELF<\/code> as the child ID. This optimization works because <code>CHILDID_SELF<\/code> is always rejected by <code>get_accChild<\/code> since it can never be the child ID of a child item. And the optimization is useful because it saves a cross-process <code>get_accChild<\/code> call.<\/p>\n<p>To encapsulate all this logic, we can use a helper function.<\/p>\n<pre>std::tuple&lt;wil::com_ptr_nothrow&lt;IAccessible&gt;, LONG&gt;\r\n    GetChild(IAccessible* pacc, VARIANT const&amp; vt)\r\n{\r\n    if (vt.vt == VT_I4) {\r\n        wil::com_ptr_nothrow&lt;IDispatch&gt; disp;\r\n        if (vt.lVal != CHILDID_SELF &amp;&amp;\r\n            SUCCEEDED(pacc-&gt;get_accChild(vt, disp.put())) &amp;&amp;\r\n            disp) {\r\n            return { disp.try_query&lt;IAccessible&gt;(), CHILDID_SELF };\r\n        } else {\r\n            return { pacc, vt.lVal };\r\n        }\r\n    } else if (vt.vt == VT_DISPATCH) {\r\n        return { wil::try_com_query_nothrow&lt;IAccessible&gt;(vt.pdispVal),\r\n                 CHILDID_SELF };\r\n    }\r\n    return {};\r\n}\r\n<\/pre>\n<p>The <code>GetChild<\/code> function takes an <code>IAccessible<\/code> and a <code>VARIANT<\/code> produced from its <code>get_accChild<\/code> and converts it to an <code>IAccessible<\/code>+<code>childId<\/code> pair that describes how to access the child item.<\/p>\n<pre>if (SUCCEEDED(AccessibleObjectFromWindow(info.hwndFocus, OBJID_CLIENT,\r\n                     IID_PPV_ARGS(acc.put()))) &amp;&amp; acc) {\r\n    wil::unique_variant vt;\r\n    if (acc-&gt;get_accFocus(&amp;vt) == S_OK) {\r\n        <span style=\"border: solid 1px currentcolor; border-bottom: none;\">auto [childAcc, childId] = GetChild(acc.get(), vt);<\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">if (childAcc) {                                    <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    ... move cursor to the child location ...      <\/span>\r\n        <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                                  <\/span>\r\n    }\r\n}\r\n<\/pre>\n<p>We&#8217;ll refactor out the &#8220;move the cursor to the object location&#8221; into a helper function since we&#8217;re using it for both the caret and for child items.<\/p>\n<pre>bool SetCursorPosToLocation(IAccessible* acc, LONG childId)\r\n{\r\n    long x, y, cx, cy;\r\n    VARIANT vt;\r\n    vt.vt = VT_I4;\r\n    vt.lVal = childId;\r\n    if (acc-&gt;accLocation(&amp;x, &amp;y, &amp;cx, &amp;cy, vt) == S_OK) {\r\n        SetCursorPos(x + cx - 1, y + cy - 1);\r\n        return true;\r\n    }\r\n    return false;\r\n}\r\n<\/pre>\n<p>That leaves us with this:<\/p>\n<pre>GUITHREADINFO info = { sizeof(GUITHREADINFO) };\r\nif (GetGUIThreadInfo(0, &amp;info))\r\n{\r\n    if (info.flags &amp; GUI_CARETBLINKING) {\r\n        MapWindowPoints(info.hwndCaret, nullptr, (POINT*)&amp;:info.rcCaret, 2);\r\n        SetCursorPos(info.rcCaret.right - 1, info.rcCaret.bottom - 1);\r\n        return;\r\n\r\n    }\r\n    if (info.hwndFocus != nullptr) {\r\n        wil::com_ptr_nothrow&lt;IAccessible&gt; acc;\r\n        if (SUCCEEDED(AccessibleObjectFromWindow(info.hwndFocus, OBJID_CARET,\r\n                             IID_PPV_ARGS(acc.put()))) &amp;&amp; acc) {\r\n            if (SetCursorPosToLocation(acc.get(), CHILDID_SELF)) {\r\n                return;\r\n            }\r\n        }\r\n        if (SUCCEEDED(AccessibleObjectFromWindow(info.hwndFocus, OBJID_CLIENT,\r\n                             IID_PPV_ARGS(acc.put()))) &amp;&amp; acc) {\r\n            wil::unique_variant vt;\r\n            if (acc-&gt;get_accFocus(&amp;vt) == S_OK) {\r\n                auto [childAcc, childId] = GetChild(acc.get(), vt);\r\n                if (childAcc &amp;&amp; SetCursorPosToLocation(childAcc.get(), childId)) {\r\n                    return;\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>But we&#8217;re not done yet. There&#8217;s another quirk we&#8217;ll have to deal with. Next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Looking at child objects.<\/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-111976","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Looking at child objects.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111976","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=111976"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111976\/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=111976"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111976"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111976"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}