Last time, we learned how to use Active Accessibility to find the caret, but what if the focus is not on an edit control? In that case, we want to find the focus object.

GUITHREADINFO info = { sizeof(GUITHREADINFO) }; if (GetGUIThreadInfo(0, &info)) { if (info.flags & GUI_CARETBLINKING) { ⟦ … ⟧ } if (info.hwndFocus != nullptr) { wil::com_ptr_nothrow<IAccessible> acc; if (SUCCEEDED(AccessibleObjectFromWindow(info.hwndFocus, OBJID_CARET, IID_PPV_ARGS(acc.put()))) && acc) { ⟦ … ⟧ } if (SUCCEEDED(AccessibleObjectFromWindow(info.hwndFocus, OBJID_CLIENT, IID_PPV_ARGS(acc.put()))) && acc) {
wil::unique_variant vt;
if (acc->get_accFocus(&vt) == S_OK) {
… to be written …
}
}
} }

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 (OBJID_CLIENT), and then ask that object for an identifier for the currently-focused child. We use a wil::unique_variant to ensure that the variant is cleaned up with Variant­Clear(), thereby avoiding a leak if the variant type requires cleanup.

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 “How child IDs are used in output parameters“.

If the identifier is in the form of a VT_DISPATCH, then convert the pdispVal to IAccessible and use CHILDID_SELF as the child ID.If the identifier is in the form of a VT_I4, then use get_accChild with the lVal to get an IDispatch, which you can then convert to IAccessible, and use CHILDID_SELF as the child ID.If the identifier is in the form of a VT_I4, but get_accChild fails, then use the original object and the lVal as the child ID.

There’s also a small optimization: If the identifier is in the form of a VT_I4 and it is already CHILDID_SELF, then use the original object with CHILDID_SELF as the child ID. This optimization works because CHILDID_SELF is always rejected by get_accChild since it can never be the child ID of a child item. And the optimization is useful because it saves a cross-process get_accChild call.

To encapsulate all this logic, we can use a helper function.

std::tuple<wil::com_ptr_nothrow<IAccessible>, LONG> GetChild(IAccessible* pacc, VARIANT const& vt) { if (vt.vt == VT_I4) { wil::com_ptr_nothrow<IDispatch> disp; if (vt.lVal != CHILDID_SELF && SUCCEEDED(pacc->get_accChild(vt, disp.put())) && disp) { return { disp.try_query<IAccessible>(), CHILDID_SELF }; } else { return { pacc, vt.lVal }; } } else if (vt.vt == VT_DISPATCH) { return { wil::try_com_query_nothrow<IAccessible>(vt.pdispVal), CHILDID_SELF }; } return {}; }

The GetChild function takes an IAccessible and a VARIANT produced from its get_accChild and converts it to an IAccessible+childId pair that describes how to access the child item.

if (SUCCEEDED(AccessibleObjectFromWindow(info.hwndFocus, OBJID_CLIENT, IID_PPV_ARGS(acc.put()))) && acc) { wil::unique_variant vt; if (acc->get_accFocus(&vt) == S_OK) { auto [childAcc, childId] = GetChild(acc.get(), vt); if (childAcc) {
… move cursor to the child location …
}
} }

We’ll refactor out the “move the cursor to the object location” into a helper function since we’re using it for both the caret and for child items.

bool SetCursorPosToLocation(IAccessible* acc, LONG childId) { long x, y, cx, cy; VARIANT vt; vt.vt = VT_I4; vt.lVal = childId; if (acc->accLocation(&x, &y, &cx, &cy, vt) == S_OK) { SetCursorPos(x + cx - 1, y + cy - 1); return true; } return false; }

That leaves us with this:

GUITHREADINFO info = { sizeof(GUITHREADINFO) }; if (GetGUIThreadInfo(0, &info)) { if (info.flags & GUI_CARETBLINKING) { MapWindowPoints(info.hwndCaret, nullptr, (POINT*)&:info.rcCaret, 2); SetCursorPos(info.rcCaret.right - 1, info.rcCaret.bottom - 1); return;

}
if (info.hwndFocus != nullptr) {
    wil::com_ptr_nothrow<IAccessible> acc;
    if (SUCCEEDED(AccessibleObjectFromWindow(info.hwndFocus, OBJID_CARET,
                         IID_PPV_ARGS(acc.put()))) && acc) {
        if (SetCursorPosToLocation(acc.get(), CHILDID_SELF)) {
            return;
        }
    }
    if (SUCCEEDED(AccessibleObjectFromWindow(info.hwndFocus, OBJID_CLIENT,
                         IID_PPV_ARGS(acc.put()))) && acc) {
        wil::unique_variant vt;
        if (acc->get_accFocus(&vt) == S_OK) {
            auto [childAcc, childId] = GetChild(acc.get(), vt);
            if (childAcc && SetCursorPosToLocation(childAcc.get(), childId)) {
                return;
            }
        }
    }
}

}

But we’re not done yet. There’s another quirk we’ll have to deal with. Next time.

The post Using Active Accessibility to find out where the focus item is appeared first on The Old New Thing.


From The Old New Thing via this RSS feed