将 UI 自动化功能添加到 Active Accessibility 服务器

没有 Microsoft UI 自动化提供程序但实现 IAccessible 的控件可以通过实现 IAccessibleEx 接口轻松升级,以提供一些UI 自动化功能。 此接口使控件能够公开UI 自动化属性和控件模式,而无需完全实现UI 自动化提供程序接口,例如 IRawElementProviderFragment。 若要实现 IAccessibleEx,基线 Microsoft Active Accessibility 对象层次结构不得包含错误或不一致(例如父对象未将其列为子对象的子对象),并且不得与UI 自动化规范冲突。 如果 Microsoft Active Accessibility 对象层次结构满足这些要求,则最好使用 IAccessibleEx 添加功能;否则,必须单独或与 Microsoft Active Accessibility 实现一起实现 UI 自动化。

以具有范围值的自定义控件为例。 控件的 Microsoft Active Accessibility 服务器定义其角色,并且能够返回其当前值,但缺少返回控件的最小值和最大值的方法,因为这些属性未在 Microsoft Active Accessibility 中定义。 UI 自动化客户端能够检索控件的角色、当前值和其他 Microsoft 活动辅助功能属性,因为UI 自动化核心可以通过 IAccessible 获取这些属性。 但是,如果不访问对象上的 IRangeValueProvider 接口,UI 自动化也无法检索最大值和最小值。

控件开发人员可以为控件提供完整的 UI 自动化提供程序,但这意味着重复 IAccessible 实现的大部分现有功能:例如导航和通用属性。 相反,开发人员可以继续依赖 IAccessible 提供此功能,同时通过 IRangeValueProvider 添加对特定于控件的属性的支持。

更新自定义控件需要执行以下主要步骤:

  • 在辅助性对象上实现 IServiceProvider,以便可以在此对象或单独的对象上找到 IAccessibleEx 接口
  • 在辅助性对象上实现 IAccessibleEx
  • 为任何 Microsoft Active Accessibility 子项创建不同的可访问对象,在 Microsoft Active Accessibility 中,这些子项可能已由父对象(例如,列表项)上的 IAccessible 接口表示。 在这些对象上实现 IAccessibleEx
  • 在所有辅助性对象上实现 IRawElementProviderSimple
  • 在辅助性对象上实现适当的控件模式接口。

本主题包含以下各节:

公开 IAccessibleEx

由于控件的 IAccessibleEx 实现可能驻留在单独的对象中,因此客户端应用程序不能依赖 QueryInterface 来获取此接口。 相反,客户端应调用 IServiceProvider::QueryService。 在此方法的以下示例实现中,假设 IAccessibleEx 不是在单独的对象上实现;因此该方法只是调用 QueryInterface

HRESULT CListboxAccessibleObject::QueryService(REFGUID guidService, REFIID riid, LPVOID *ppvObject)
{
    if (!ppvObject)
    {
        return E_INVALIDARG;
    }
    *ppvObject = NULL;
    if (guidService == __uuidof(IAccessibleEx))
    {
        return QueryInterface(riid, ppvObject);
    }
    else 
    {
        return E_INVALIDARG;
    }
};

实现 IAccessibleEx

IAccessibleEx 中最受关注的方法是 GetObjectForChild。 此方法使 Microsoft Active Accessibility 服务器有机会为子项创建可访问对象(至少公开 IAccessibleEx)。 在Microsoft Active Accessibility 中,子项通常不表示为可访问对象,而表示为可访问对象的子项。 但是,由于 UI 自动化要求每个元素都由单独的可访问对象表示,因此 GetObjectForChild 必须为每个按需子元素创建单独的对象。

以下示例实现返回自定义列表视图中项的可访问对象。

HRESULT CListboxAccessibleObject::GetObjectForChild(long idChild, IAccessibleEx **pRetVal)
{ 
    *pRetVal = NULL;
    VARIANT vChild;
    vChild.vt = VT_I4;
    vChild.lVal = idChild;

    // ValidateChildId is an application-defined function that checks whether
    // the child ID is valid. This is similar to code that validates the varChild
    // parameter in IAccessible methods.
    //
    // Additionally, if this idChild corresponds to a child that has its own
    // IAccessible, we should also return E_INVALIDARG here. (The caller
    // should instead be using the IAccessibleEx from that child's own
    // IAccessible in that case.)
    if (idChild == CHILDID_SELF || FAILED(ValidateChildId(vChild)))
    {
        return E_INVALIDARG;
    }

    // Return a suitable provider for this specific child.
    // This implementation returns a new instance each time; an implementation
    // can cache these if desired.

    // _pListboxControl is a member variable pointer to the owning control.
    IAccessibleEx* pAccEx  = new CListItemAccessibleObject(idChild, _pListboxControl);
    if (pAccEx == NULL)
    {
        return E_OUTOFMEMORY;
    }
    *pRetVal = pAccEx;
    return S_OK; 
}

有关完整的示例实现,请参阅使自定义控件可访问第 5 部分:使用 IAccessibleEx 向自定义控件添加UI 自动化支持

UI 自动化提供程序程序员指南