创建更易理解的动态注释用户界面

创建更易理解的动态注释用户界面

Rob Sinclair 和 Brendan McKeon
Microsoft Corporation
2000年9月

 

摘要: :作为 Microsoft Active Accessibility 2.0 系列的第一篇文章,本文论述了动态注释 (DA),一种使开发人员提高用户界面可用性的新技术。

目录



简介

前面的文章介绍了作为 Microsoft Active Accessibility 1.0 的一部分的 IAccessible 界面,并说明了 oleacc.dll 为标准控件(USER 和 ComCtl)提供了该界面的默认实现。这些文章使开发人员在使用自己绘制的和自定义控件时避免破坏默认实现。有关详细信息,请参阅 Microsoft Active Accessibility: Part 1(英文)(另在 MSDN News 的 2000 年 5/6 月刊) Microsoft Active Accessibility: Part 2(英文)(另在 MSDN News 的 2000 年 7/8 月刊)。

本文是讨论 Active Accessibility 2.0 及其新功能系列文章的第一篇。作为这些功能之一的动态注释 (DA) 与前面文章中讨论的技术相比,为自定义辅助功能信息提供了更好的灵活性并明显降低了开发成本。



什么是动态注释

Active Accessibility 组件 oleacc.dll 创建代表标准 Microsoft Windows? 控件实现 IAccessible 的代理对象。由于这些代理使用标准 Windows 消息和控件专用 API 收集每个控件的信息,因此没有直接的机制自定义这些代理通过 IAccessible 提供的信息。

动态注释允许开发人员将提示和其他有用信息传递给 OLEACC 来自定义其提供的信息。该功能本身将降低支持 Active Accessibility 的成本,使开发人员大大改进用户界面的辅助功能。



动态注释的替代方法

还有其他方法可以为界面元素提供自定义 IAccessible 支持,在某些场合它们是正确的解决方案。实际上,在引入注释之前,这些技术是开发人员的唯一选择。

第一种替代方法是实现整个 IAccessible 界面。自定义控件或不同用户界面元素通常需要该方法,但是开发和测试成本明显太高,因此除非真正必要否则应该避免采用。如果目标是更改单个属性,则成本难以判断。

第二种方法是使用子类和封装技术修改指定属性的信息。这是动态注释要替代的技术。要使用子类和封装重载单个属性,开发人员必须:

  • 使 IAccessible 对象的 HWND 成为子类。

  • 截取 WM_GETOBJECT 获得正确 lParam/OBJID 值。

  • 将 WM_GETOBJECT 通过 CallWndProc() 传递给基类。如果返回 0,则调用 CreateStdAccessibleObject;否则调用 LresultFromObject 来获得控件本身的 IAccessible

  • 创建封装类,该类实现 IAccessible 并封装了上一步返回的 IAccessible。除需要重载的之外,该封装类将所有方法和属性均传递给原始 IAccessible。这包括编写传递 IAccessible 所有 21 种属性和方法的代码,无论实际重载多少。

  • 重载方法和属性必须只处理所需的子 ID,而将所有其他内容传递给原始 IAccessible

  • 如果且仅在原始对象支持的情况下,封装还必须传递 IEnumVARIANTIOleWindow 接口。

  • 必须仔细确保正确处理引用计算,特别是如果支持其它界面的话。

  • 必须仔细正确处理 IDispatch,特别是 ITypeLib::Invoke(),它必须随指向封装接口的接口指针一起调用,而不是指向原始 IAccessible 接口的指针。

如前所见,这包括相当的工作量,即使只有一个或两个属性需要重载。结果代码的大部分用于子类和封装,只有一小部分结果代码实际执行真正提供重载信息的任务。



为用户界面添加注释

Active Accessibility 2.0 中支持三种动态注释:直接注释数值映射注释回调注释。每种类型都有它的优点,因此首先要理解哪一种方法适用于指定场合。

注释接口

在继续之前,熟悉注释所涉及的三种主要接口非常重要。由于篇幅所限,我们只对每一种进行高层次的说明。有关详细信息,包括每个界面方法的讨论,可在 Active Accessibility SDK 中找到。

IAccPropServices 接口。这是由单个类 CLSID_AccPropServices 实现的主要注释接口,该类位于 oleacc.dll 中。该接口包含注释访问元素的方法(请参阅表 1)。

表 1. IAccPropServices 接口方法

SetPropValue()需要通过一个 VARIANT 变量传递 IAccessibleIdentity 和注释值。
SetPropServer()用于回调注释。
ClearProps()阐述属性的注释值,但是这通常不需要,因为注释服务在注释元素销毁时自动清除并释放该信息。
SetHwndProp()

SetHwndPropStr()

SetHwndPropServer()

这些方法提供 HWND 专用注释。
SetHmenuProp()

SetHmenuPropStr()

SetHmenuPropServer()

这些方法提供 HMENU 专用注释。

IAccIdentity 接口。IAccessible 对象实现并内部用于进入注释数据库的密匙。所有希望支持动态注释的对象必须支持该接口。

IAccPropServer 接口。由在回调注释中使用的回调对象实现。

直接注释

动态注释的最简单形式称作直接注释。这种注释最适用于注释属性不依赖于控件状态的访问元素,因此不需要由数值映射注释回调注释提供的附加支持。

要使用直接注释为访问元素进行注释:

  • 获得指向要注释的访问元素的 IAccessible 接口指针。

  • IAccIdentity 接口的 QI,并调用 GetIdentityString() 获得访问元素的标识字符串。

  • 同时创建 AccPropServices 对象。

  • 调用 IAccPropServices::SetPropValue,传递标识字符串,代表要重载的属性的 GUID,一个包含新属性值的 VARIANT 变量。

如果不对访问元素的标识调用 SetPropValue,开发人员可以调用 SetHwndPropXSetHmenuPropX 方法并提供访问元素的 HWND/HMENU、对象 ID 和子 ID。这不需要 IAccIdentity 的 QI,调用 GetIdentityString 并在 VARIANT 中包装字符串值。

下列属性可使用直接注释进行注释,数值必须为指定类型:

PROPID_ACC_DEFAULTACTION(VT_BSTR)
PROPID_ACC_DESCRIPTION(VT_BSTR)
PROPID_ACC_HELP(VT_BSTR)
PROPID_ACC_KEYBOARDSHORTCUT(VT_BSTR)
PROPID_ACC_NAME(VT_BSTR)
PROPID_ACC_ROLE(VT_I4)
PROPID_ACC_STATE(VT_I4)
PROPID_ACC_VALUE(VT_BSTR)

示例 1:设置对话框中图标的 Description 属性。

#include <oleacc.h>
...

BOOL CALLBACK DialogProc(   HMENU Hmenu,
                  UINT uMsg,
                  WPARAM wParam,
                  LPARAM lParam )
{
   switch( uMsg )
   {
      case WM_INITDIALOG:
      {
         IAccPropServices * pAccPropSvc = NULL;
         HRESULT hr = CoCreateInstance(   CLSID_AccPropServices,
                  NULL, 
                  CLSCTX_SERVER,
                  IID_IAccPropServices,
                  (void **) & pAccPropSvc );

         if( hr == S_OK && pAccPropSvc )
         {
            HMENU HmenuIcon = GetDlgItem(   Hmenu, IDC_ICON1);
            pAccPropSvc->SetHmenuPropStr(   HandleToLong(HmenuIcon),
                        OBJID_CLIENT,
                        0,
                        PROPID_ACC_DESCRIPTION,
                  L"Picture of a thermometer");
            pAccPropSvc->Release();
         }
         ...
      }
      ...
   }
   ...
}

数值映射注释

除了直接注释 IAccessible 属性外,经常需要将控件专用数值或索引转换为用户可理解的字符串。一个示例就是显示属性,设置选项卡上的屏幕分辨率游标。尽管每个游标位置对应于不同的分辨率(例如,640 x 480,1024 x 768),但是控件并不知道这种对应关系,因此不能将该信息传递给 Active Accessibility。只有使用该控件的开发人员才能提供这种关系。

幸运的是,利用 Active Accessibility 2.0 的数值映射注释,该任务极为简单。要使用这种注释,开发人员定义从游标位置到显示分辨率的映射并将其传递给注释服务。Active Accessibility 将控件内部值映射到应该显示的属性字符串。

支持的映射

由于需要控件专用知识来支持映射,因此只有有限数量的控件和属性支持数值映射注释。

游标数值映射:PROPID_ACC_VALUEMAP
由 LEACC 游标(也称为轨迹条)代理所支持,该属性包含从内部游标位置到人类可读字符串的映射。如果当前游标值存在于数值映射中,将显示对应的字符串,替换默认的百分比字符串(例如,"50")。

这在游标用于从有限的离散选项中选择一个选项的场合很有用。

ListView 和 TreeView 数值映射:PROPID_ACC_ROLEMAP, PROPID_ACC_STATEMAP

由 OLEACC ListView 和 TreeView 代理所支持,这些映射提供从状态图象索引到角色和状态值的映射。

某些 ListViews 和 TreeViews 使用外观如复选框和单选按钮的状态图象来实现列表或复选框和单选选项树。这些映射使这些状态图象映射到相应的角色(典型情况为 ROLE_SYSTEM_RADIOBUTTON 或 ROLE_SYSTEM_CHECKBOX)和附加状态位(典型情况为 STATE_SYSTEM_CHECKED)。

注释状态值与 OLEACC(例如焦点和可见性)使用位 OR 运算符 (|) 计算出的状态位相结合。

映射可以使用 TreeView 或 ListView 项的图象索引、状态图象索引或覆盖图象索引作为关键字,并分别以 0、1 或 2 等索引关键字表示。

注释映射格式

注释映射包含一系列以分隔字符分隔的字段

  • 大写字母 'A':表示使用了该特殊编码方案。未来的编码方案可能支持附加前缀。

  • 分隔字符:典型情况使用分号 (':'),但是这可以是除空 (NUL) 或空格以外的任何字符。由于该字符将用于分隔字段的分隔符,因此不能用作映射值的一部分。

  • 表示使用哪个关键字的值:对于 TreeViewListView 角色和状态映射,该关键字可以为表示各个图象索引、状态图象索引或覆盖图象索引的 0、1 或 2。对于游标或其他没有提供关键字选项的控件,该值必须为 0。

  • 分隔符

  • 一系列键-值对,每对由下列内容组成:
    • 键字符串:通常为数字,可以为十进制或十六进制(以 "0x" 前缀开头)。

    • 分隔符

    • 值字符串:这是数值映射中的字符串和角色和状态映射中的数字(可以使用十进制或十六进制)。

    • 分隔符

映射字符串的示例包括:

A:0:0:Cold:1:Warm:3:Hot:

如果数值映射应用于游标控件,游标在位置 1 时将显示 "Warm"。请注意该示例映射中没有值 2,因此在该位置时将显示默认值(在游标中,应该是百分比值 "33")。

A:1:0:34:1:0x2C:

该映射中的初始值 '1' 表示状态图象索引(而不是图象索引或覆盖图象索引)将用作关键字。当用作 TreeView 的角色映射时,如果该项的状态图象索引为 0,则角色将以 ROLE_SYSTEM_LISTITEM(对应于十进制值 34)显示。对于状态图象索引为 1 的项,其角色将显示为 ROLE_SYSTEM_CHECKBUTTON(对应于十六进制值 0x2C)。具有其他状态图象索引的 TreeView 项将具有 TreeView 项的默认角色 ROLE_SYSTEM_OUTLINEITEM。

回调注释

回调注释为 Active Accessibility 2.0 支持的第三种注释。它允许开发人员注册回调对象响应元素注释属性客户请求。该回调对象必须实现 IAccPropServer 接口并注册为 Active Accessibility 注册服务。一旦注册,它将负责响应所有访问元素属性注释的客户请求。

要使用回调注释注释对象属性:

  • 获得指向要注释的访问元素的 IAccessible 接口指针。

  • IAccIdentity 接口的 QI,并调用 GetIdentityString() 获得访问元素的标识字符串。

  • 同时创建 AccPropServices 对象。

  • 创建实现 IAccPropServer 的 COM 对象。

  • 调用 IAccPropServices::SetPropServer,传递标识字符串,表示要重载属性的 GUID,和指向 IAccPropServer 回调对象的指针。

  • 当有客户端请求访问元素的注释属性时,将调用回调对象提供正确的值。

使用直接注释时,SetHwndPropServerSetHmenuPropServer 方法可用于指定 HWND/HMENU、对象 ID 和子 ID,而不是检索标识字符串并用其调用 SetPropServer。在容器对象上使用 SetPropServer 时,开发人员还可以指定注释应该应用于该容器的所有子元素。SetHwndPropServerSetHmenuPropServer

下列属性只能使用回调注释进行注释,返回值通常为 VT_DISPATCH 变量中的 IAccessible

PROPID_ACC_FOCUS

PROPID_ACC_SELECTION

PROPID_ACC_PARENT

PROPID_ACC_NAV_UP

PROPID_ACC_NAV_DOWN

PROPID_ACC_NAV_LEFT

PROPID_ACC_NAV_RIGHT

PROPID_ACC_NAV_PREV

PROPID_ACC_NAV_NEXT

PROPID_ACC_NAV_FIRSTCHILD

PROPID_ACC_NAV_LASTCHILD

示例 2:根据 ListView 项的需求提供自定义帮助字符串。

在本示例中,开发人员需要为 ListView 中的每一项提供自定义帮助字符串,并且希望仅在客户端特殊请求时提供:

class ListViewAccServer: public IAccPropServer
{
   ULONG   m_Ref;
   IAccPropServices * m_pAccPropSvc;

public:
   ListViewAccServer( IAccPropServices * pAccPropSvc )
      : m_Ref( 1 ),
        m_pAccPropSvc( pAccPropSvc )
   {
      m_pAccPropSvc->AddRef();
   }

   ~ListViewAccServer()
   {
      m_pAccPropSvc->Release();
   }

忽略了 AddRef()Release()QueryInterface()

   HRESULT STDMETHODCALLTYPE GetPropValue ( 
      const BYTE *    pIDString,
      DWORD           dwIDStringLen,
      MSAAPROPID      idProp,
      VARIANT *       pvarValue,
      BOOL *          pfGotProp )
   {
      // Default return values, in case we need to bail out?
      *pfGotProp = FALSE;
      pvarValue->vt = VT_EMPTY;
      // Extract the idChild from the identity string?
      DWORD dwHmenu, idObject, idChild;
      HRESULT hr = m_pAccPropSvc->DecomposeHmenuIdentityString(
                        pIDString, dwIDStringLen, 
                        & dwHmenu, & idObject, 
                        & idChild );
      if( hr != S_OK )
      {
         return S_OK;
      }

      HMENU Hmenu = (HMENU)HandleToLong( dwHmenu );
      // Only supply help string for child elements, not the // listview itself?if
( idChild == CHILDID_SELF ) { return S_OK; } // GetHelpString returns a UNICODE string corresponding to // the index it is passed. LPWSTR pHelpString = GetHelpString( idChild ? ); if( ! pHelpString ) { return S_OK; } BSTR bstr = SysAllocString( pHelpString ); pvarValue->vt = VT_BSTR; pvarValue->bstrVal = bstr; *pfGotProp = TRUE; return S_OK; } };

在创建 ListView HmenuLV 之后:

IAccPropServices * pAccPropSvc = NULL;
HRESULT hr = CoCreateInstance( CLSID_PropMgr,
                  NULL, 
                  CLSCTX_SERVER,
                  IID_IAccPropServices,
                  (void **) & pAccPropSvc );
if( hr == S_OK && pAccPropSvc )
{
   ListViewAccServer * pLVServer = new ListViewAccServer( pAccPropSvc );
   if( pLVServer )
   {
      MSAAPROPID propid = PROPID_ACC_HELP;
      pAccPropSvc->SetHmenuPropServer( DWORD( HmenuLV ),
                  OBJID_CLIENT, 0,
                  1, & propid,
                  pLVServer, ANNO_CONTAINER );
      pLVServer->Release();
      pAccPropSvc->Release();
   }
}


可用性和其他资源

由于这是一项新功能,Active Accessibility 2.0 中的 OLEACC 代理仅为当前支持动态注释的对象。但是这种机制可用于自定义为实现 IAccessibleIdentity 接口的对象提供的 IAccessible 信息。

Active Accessibility 2.0 将置于 Microsoft Windows 操作系统的下一主要版本中,代码命名为 Whistler。还可能作为对现有 Windows 版本的更新。有关详细信息,请参阅 Microsoft Accessible 技术组的 Web 站点 http://www.microsoft.com/enable/(英文)。

要查看 Active Accessibility 2.0 的操作,请立即加入 Whistler 测试程序。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值