確保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

在上一個螢幕快照中,請注意物件階層與控件的結構不符,因為它們出現在名稱專案窗體的使用者介面中。 另請注意, Inspect 已將不正確的名稱指派給下一個到最後一個專案(這是輸入名字的編輯控件,而且應該是名為 “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

若要更正名稱專案表單的問題,應該編輯 resource (.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,或從 [格式] 功能選取 Tab Order,以存取 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”) 和 maximum (“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 元件 Oleacc.dll 中包含的預設 Proxy 會自動為所有標準 Windows 控件提供 IAccessible 物件。 如果您自定義標準 Windows 控件,預設 Proxy 會盡最大努力提供自定義控件的所有 IAccessible 屬性。 您應該徹底測試自定義控件,以確保預設 Proxy 提供正確且完整的屬性值。 如果測試顯示不正確的或不完整的屬性值,您可以使用稱為直接註釋的動態註釋技術來提供正確的屬性值,並新增遺漏的屬性值。

請注意,動態註釋不只是針對 Microsoft Active Accessibility Proxy 所支援的控件。 您也可以使用它來修改或提供任何提供自己 IAccessible 實作之控件的屬性。

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

標註 Name 屬性的步驟

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

  1. 包含下列頭檔:

    • Initguid.h
    • Oleacc.h

    注意

    若要定義 GUID,您必須在相同檔案的 Oleacc.h 之前包含 Initguid.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。 (如需您可以透過直接註釋設定的其他屬性清單,請參閱使用直接註釋。)Str SetHwndPropStr 的最後一個參數是做為 Name 屬性的新字串。

標註 Name 屬性的範例

下列範例程式代碼示範如何使用直接註釋來變更控件之 IAccessible 物件的 Name 屬性。 為了保持簡單,此範例會使用硬式編碼字串 (“New Control 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;
}