確保 UI 元素已正確命名

本主題描述在 Microsoft Win32 應用程式中指定 UI 元素名稱的正確方式,讓 Microsoft Active Accessibility 可以透過IAccessibleName屬性正確地向用戶端應用程式公開名稱。

本節中的資訊僅適用于 Microsoft Active Accessibility。 它不適用於使用 Microsoft 消費者介面自動化 的應用程式,或根據 HTML、動態 HTML (DHTML) 或 XML 等標記語言的應用程式。

概觀

在 Microsoft Active Accessibility 中,應用程式中的每個 UI 元素都會以公開 IAccessible 介面的物件來表示。 用戶端應用程式會使用 IAccessible 介面的屬性和方法與 UI 元素互動,並擷取其相關資訊。 IAccessible介面所公開的最重要屬性之一是Name 屬性。 用戶端應用程式依賴 Name 屬性來尋找、識別或宣告使用者 UI 元素。 如果 Microsoft Active Accessibility 無法正確公開特定 UI 元素的 Name 屬性,用戶端應用程式將無法向使用者呈現該 UI 元素,而且殘障使用者將無法存取 UI 元素。

命名不正確造成問題的方式

若要說明 UI 元素命名不正確所造成的問題,請考慮下圖所示的名稱專案表單。

illustration of a simple form for entering first and last name

雖然表單中的 UI 元素看起來沒問題,但程式設計實作不正確。 若要讓 Microsoft Active Accessibility 用戶端例如螢幕助讀程式,頂端編輯控制項的 Name 屬性 是 「Last Name:」,而底部編輯控制項的 Name 屬性是空字串 (「) 。 螢幕助讀程式會將頂端編輯控制項讀為「姓氏」,不過使用者預期會輸入名字。 螢幕助讀程式會將第二個編輯控制項讀為「無名稱」,因此使用者不知道要輸入第二個編輯控制項的內容。 螢幕助讀程式無法協助使用者將資料輸入到這個簡單表單中。

表單的另一個問題是,不會將任何快速鍵指派給其中一個編輯控制項。 使用者被強制使用索引標籤到控制項,或使用滑鼠。

下列各節說明這些問題的來源,並提供更正它們的指導方針。

MSAA 如何取得 Name 屬性

Microsoft Active Accessibility 會根據 UI 元素的類型,從不同的位置取得 Name 屬性 字串。 對於具有相關聯視窗文字的大部分 UI 元素,Microsoft Active Accessibility 會使用視窗文字做為 Name 屬性字串。 這種類型的 UI 元素範例包括按鈕、功能表項目和工具提示等控制項。

針對下列控制項,Microsoft Active Accessibility 會忽略視窗文字,而是尋找靜態文字標籤 (或群組方塊標籤,) 緊接在索引標籤順序的控制項前面。

  • 下拉式方塊
  • 日期和時間選擇器
  • 編輯和豐富編輯控制項
  • IP 位址控制項
  • 清單方塊
  • 清單檢視
  • 進度列
  • 捲軸
  • 具有SS_ICON或SS_BITMAP樣式的靜態控制項
  • 追蹤列
  • 樹狀檢視

如果上述控制項未隨附靜態文字標籤,或標籤未正確實作,Microsoft Active Accessibility 就無法將正確的 Name 屬性 提供給用戶端應用程式。

上述大部分控制項實際上都有相關聯的視窗文字。 資源編輯器會自動產生視窗文字,其中包含泛型字串,例如 「edit1」 或 「listbox3」。 雖然開發人員可以使用更有意義的文字來取代產生的視窗文字,但大部分都不會這麼做。 由於產生的視窗文字對使用者沒有任何意義,因此 Microsoft Active Accessibility 會忽略它,並改用隨附的靜態文字標籤。

如何尋找和更正命名問題

在 [命名不正確造成問題的方式] 中顯示的名稱專案表單中,問題的原因是控制項的定位順序不正確。 使用 檢查 之類的測試控管檢查 UI,會顯示物件階層的問題。 下列螢幕擷取畫面顯示名稱專案表單在 [檢查] 中顯示的中斷物件階層。

screen shot of the inspect tool showing an incorrect object hierarchy of the name entry form

在上一個螢幕擷取畫面中,請注意,物件階層與控制項的結構不符,因為它們出現在名稱專案表單的使用者介面中。 另請注意, 檢查 已將不正確的名稱指派給下一個到最後一個專案, (它是輸入名字的編輯控制項,而且應該是名為 「First Name:」。 最後,請注意,檢查找不到最後一個專案的名稱, (它是輸入姓氏的編輯控制項,且名稱應為 「Last Name:」。

下列範例顯示名稱專案表單之資源檔的內容。 請注意,定位順序與控制項在使用者介面中顯示的邏輯結構不一致。 另請注意,兩個編輯控制項沒有指定快速鍵。

IDD_INPUTNAME DIALOGEX 22, 17, 312, 118
STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
CAPTION "Enter your name"
FONT 8, "System", 0, 0, 0x0
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,179,35,30,11,WS_GROUP
    LTEXT           "First Name:",IDC_STATIC,8,16,43,8
    LTEXT           "Last Name:",IDC_STATIC,8,33,43,8
    EDITTEXT        IDC_EDIT1,53,15,120,12,ES_AUTOHSCROLL
    EDITTEXT        IDC_EDIT2,53,34,120,12,ES_AUTOHSCROLL
END

若要更正名稱專案表單的問題,應該編輯資源 (.rc) 檔案以指定鍵盤快速鍵,且控制項應依下列順序排列:

  1. 「 & First Name:」 靜態文字標籤。
  2. 輸入名字 (IDC_EDIT1) 的編輯控制項。
  3. 「 & Last Name:」 靜態文字標籤。
  4. 輸入姓氏 (IDC_EDIT2) 的編輯控制項。
  5. [確定] 預設按鈕。

下列範例顯示名稱專案表單的更正資源檔:

IDD_INPUTNAME DIALOGEX 22, 17, 312, 118
STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
CAPTION "Enter your name"
FONT 8, "System", 0, 0, 0x0
BEGIN
    LTEXT           "&First Name:",IDC_STATIC,8,16,43,8
    EDITTEXT        IDC_EDIT1,53,15,120,12,ES_AUTOHSCROLL
    LTEXT           "&Last Name:",IDC_STATIC,8,33,43,8
    EDITTEXT        IDC_EDIT2,53,34,120,12,ES_AUTOHSCROLL
    DEFPUSHBUTTON   "OK",IDOK,179,35,30,11,WS_GROUP
END

若要對資源檔進行更正,您可以直接編輯檔案,或使用Microsoft Visual Studio中的 Tab Order 工具。 您可以按 CTRL+D,或從 [格式] 功能表中選取 [定位順序],在 Visual Studio中存取Tab Order工具。

更正和重建應用程式之後,名稱專案表單的 UI 看起來會與之前所做的相同。 不過,Microsoft Active Accessibility 現在會將正確的 Name 屬性提供給用戶端應用程式,並在使用者按下 ALT+F 或 ALT+L 鍵盤快速鍵時正確設定焦點。 此外, 檢查 也會顯示正確的物件階層,如下列螢幕擷取畫面所示。

screen shot of the accessible explorer tool showing a correct object hierarchy of the name entry form

如何正確命名追蹤列

定義追蹤列 (或滑杆) 時,請確定追蹤列的主要靜態文字標籤會出現在追蹤列之前,而且追蹤列後面會出現最小和最大範圍的靜態文字標籤。 請記住,Microsoft Active Accessibility 會使用緊接在控制項前面的靜態文字標籤,做為控制項的 Name 屬性 。 將主要靜態文字標籤放在追蹤列之前,以及追蹤列之後的其他標籤,可確保 Microsoft Active Accessibility 為用戶端提供正確的 Name 屬性。

下圖顯示一般追蹤列,其中包含名為 「Speed」 的主要靜態文字標籤,以及最小 (「min」) 和最大 (「max」) 範圍的靜態文字標籤。

illustration of a trackbar control that has a main label and labels for the minimum and maximum ranges

下列範例示範在資源檔中定義追蹤列及其靜態文字標籤的正確方式:

BEGIN
    ...

    LTEXT           "&Speed",IDC_STATIC,47,20,43,8
    CONTROL         "",IDC_SLIDER1,"msctls_trackbar32",
                    TBS_AUTOTICKS | TBS_BOTH | WS_TABSTOP,
                    32,32,62,23
    LTEXT           "min",IDC_STATIC,16,37,15,8
    LTEXT           "max",IDC_STATIC,94,38,43,8

    ...
END

如何使用不可見標籤命名控制項

不一定可以或想要讓每個控制項都有可見的標籤。 例如,有時候新增標籤可能會導致 UI 外觀不想要的變更。 在此情況下,您可以使用不可見的標籤。 Microsoft Active Accessibility 仍會挑選與不可見標籤相關聯的文字,但標籤不會出現在視覺 UI 中或干擾。

如同可見標籤,不可見標籤必須緊接在定位順序的控制項前面。 若要在資源檔 (.rc) 中隱藏標籤,請將 或 |~WS_VISIBLE 新增 NOT WS_VISIBLE 至靜態文字控制項的樣式部分。 如果您在 Visual Studio 中使用資源編輯器,您可以將 Visible 屬性設定為 False。

如何使用直接注釋來指定 Name 屬性

Microsoft Active Accessibility Runtime 元件中包含的預設 Proxy Oleacc.dll會自動為所有標準Windows控制項提供IAccessible物件。 如果您自訂標準Windows控制項,預設 Proxy 會盡最大努力為您的自訂控制項提供所有IAccessible屬性。 您應該徹底測試自訂控制項,以確保預設 Proxy 提供精確且完整的屬性值。 如果測試顯示不正確的或不完整的屬性值,您可以使用稱為直接注釋的動態注釋技術來提供正確的屬性值,並新增遺漏的屬性值。

請注意,動態注釋不只是 Microsoft Active Accessibility Proxy 所支援的控制項。 您也可以使用它來修改或提供任何提供其本身 IAccessible 實作之控制項的屬性。

本節著重于使用直接注釋,為控制項的IAccessible物件的Name 屬性提供正確的值。 您也可以使用直接注釋來提供其他屬性值。 此外,除了直接注釋之外,還有其他動態注釋技術可供使用,而且動態注釋 API 的特性和功能遠超出本節中所述的範圍。 如需動態注釋的詳細資訊,請參閱 動態注釋 API

標注 Name 屬性的步驟

使用直接注釋來變更控制項的 Name 屬性 牽涉到下列步驟。

  1. 包含下列標頭檔:

    • Initguid.h
    • Oleacc.h

    注意

    若要定義 GUID,您必須在相同的檔案中包含 Initguid.h 之前 Oleacc.h。

     

  2. 呼叫 CoInitializeEx 函式,通常是在應用程式初始化程式期間,初始化元件物件模型 (COM) 程式庫。

  3. 在建立目標控制項之後,一般會在 WM_INITDIALOG 訊息) 期間建立目標 (控制項之後,建立批註管理員的實例,並取得其 IAccPropServices 指標的指標。

  4. 使用IAccPropServices::SetHwndPropStr方法標注目標控制項的Name 屬性

  5. 釋放 IAccPropServices 指標。

  6. 在處理 (WM_DESTROY訊息) 時,通常會在目標控制項終結之前,請建立批註管理員的實例,並取得其 IAccPropServices 介面的指標。

  7. 使用 IAccPropServices::ClearHwndProps 方法可從目標控制項清除 Name 屬性 批註。

  8. 釋放 IAccPropServices 指標。

  9. 在應用程式結束 (之前,通常會在處理 WM_DESTROY 訊息) 時,呼叫 CoUninitialize 函式來釋放 COM 程式庫。

IAccPropServices::SetHwndPropStr函式採用五個參數。 前三個 - hwndidObjectidChild - 結合以識別控制項。 第四個參數 idProp會指定要變更之屬性的識別碼。 若要變更 Name 屬性,請將 idProp 設定為 PROPID_ACC_NAME。 (如需可透過直接注釋設定的其他屬性清單,請參閱 使用 Direct Annotation.) SetHwndPropStr的最後一個參數 str是做為 Name 屬性的新字串。

標注 Name 屬性的範例

下列範例程式碼示範如何使用直接注釋來變更控制項之IAccessible物件的Name 屬性。 為了保持簡單,此範例會使用硬式編碼字串 (「新增控制項名稱」) 來設定 Name 屬性。 硬式編碼字串不應該用於應用程式的最終版本,因為它們無法當地語系化。 相反地,請一律從資源檔載入字串。 此外,此範例不會顯示 對 CoInitializeExCoUninitialize 函式的呼叫。

#include <initguid.h>
#include <oleacc.h>

// AnnotateControlName - Uses direct annotation to change the Name property 
// of the IAccessible object for a control.
//
// hDlg - Handle of the dialog box that contains the control.
// hwndCtl - Handle of the control whose Name property is to be changed.
HRESULT AnnotateControlName(HWND hDlg, HWND hwndCtl)
{
    HRESULT hr;        

    IAccPropServices *pAccPropSvc = NULL;  

    // Create an instance of the annotation manager and retrieve the 
    // IAccPropServices pointer.
    hr = CoCreateInstance(CLSID_AccPropServices, NULL, CLSCTX_SERVER, 
        IID_IAccPropServices, (void **) &pAccPropSvc);

    if (hr != S_OK || pAccPropSvc == NULL)
        return hr;

    // Set the Name property for the control.
    // Note: A hard-coded string is used here to keep the example simple.
    // Always use localizable string resources in your applications. 
    hr = pAccPropSvc->SetHwndPropStr(hwndCtl, OBJID_CLIENT, CHILDID_SELF, 
        PROPID_ACC_NAME, L"New Control Name");

    pAccPropSvc->Release();
    
    return hr;
}

// RemoveAnnotatedNameFromControl - Removes the annotated name from the 
// Name property of the IAccessible object for a control.
//
// hDlg - Handle of the dialog box that contains the control.
// hwndCtl - Handle of the control whose annotated name is to be removed.
HRESULT RemoveAnnotatedNameFromControl(HWND hDlg, HWND hwndCtl)
{
    HRESULT hr;

    IAccPropServices *pAccPropSvc = NULL;

    // Create an instance of the annotation manager and retrieve the 
    // IAccPropServices pointer.
    hr = CoCreateInstance(CLSID_AccPropServices, NULL, CLSCTX_SERVER, 
        IID_IAccPropServices, (void **) &pAccPropSvc);

    if (hr != S_OK || pAccPropSvc == NULL)
        return hr;

    // Remove the annotated name from the Name property for the control.
    MSAAPROPID propid = PROPID_ACC_NAME;
    hr = pAccPropSvc->ClearHwndProps(hwndCtl, OBJID_CLIENT, CHILDID_SELF, 
        &propid, 1);

    // Release the annotation manager.
    pAccPropSvc->Release();

    return hr;
}

概念

動態注釋 API

提供 Name 屬性

測試控管