WinUI 2 + C++/WinRT で XAML Islands を使うと、Narrator が「○○ ― コンボ ボックス」のように技術的な種別名まで読み上げ、利用者の理解を妨げることがあります。本記事では UI Automation の仕組みを整理しながら、AutomationProperties.LocalizedControlType を用いて「必要な情報だけ」を読み上げる実践的な設計・実装・検証手順を詳解します。
問題の正体:なぜ Narrator は「種別」まで読み上げるのか
Windows のスクリーンリーダー(Narrator など)は UI Automation (UIA) のプロパティを参照し、概ね「Name + LocalizedControlType + 状態」の順で読み上げを合成します。たとえば WinUI の ComboBox は既定で コンボ ボックスという種別名 (LocalizedControlType) を持つため、AutomationProperties.Name に「Options Dropdown」を設定していても、結果として「Options Dropdown — コンボ ボックス」と読み上げられます。
これは UIA の設計上、ControlType(論理的な役割)に紐づくローカライズ済みの種別名が自動的に付与されるためです。つまり、読み上げから種別を取り除きたければ、UIA が参照する種別名そのものを置き換える必要があります。
解決の要点:AutomationProperties.LocalizedControlType を上書きする
UIA の読み上げにおける種別文字列は、AutomationProperties.LocalizedControlType で上書きできます。既定の「コンボ ボックス」「ボタン」などの語を、用途が直感的な表現(例:「オプション選択メニュー」)に差し替えることで、Narrator は「Options Dropdown — オプション選択メニュー」のように、技術的な種別語を出さずに、ユーザーに伝えたい言葉に置き換えて読みます。さらに HelpText に短い補足を入れておくと、必要なときにだけ説明を深掘りできます。
最小実装例(C++/WinRT)
// 必要なヘッダー
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Automation.h>
using namespace winrt;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Automation;
// コンボボックス生成
ComboBox myComboBox;
myComboBox.PlaceholderText(L"項目を選択");
// アイテム追加
myComboBox.Items().Append(winrt::box_value(L"選択肢 1"));
myComboBox.Items().Append(winrt::box_value(L"選択肢 2"));
// アクセシビリティ設定
AutomationProperties::SetName(myComboBox, L"Options Dropdown");
AutomationProperties::SetLocalizedControlType(myComboBox, L"オプション選択メニュー"); // 種別読み上げを上書き
AutomationProperties::SetHelpText(myComboBox, L"一覧から 1 つ選んでください。");
AutomationProperties::SetIsRequiredForForm(myComboBox, true);
読み上げ文を「役立つ日本語」に整える設計指針
- 完全に黙らせない: 種別語を消すのではなく、用途が直感的な短い日本語に置き換える(例:「ドロップダウン」ではなく「オプション選択メニュー」)。
- 冗長さを避ける: Name と LocalizedControlType で意味が重複しないようにする(悪例:「設定メニュー — 設定メニュー」)。
- 補助説明は HelpText に: 操作方法・入力規則・必須性は
HelpTextとIsRequiredForFormに集約する。 - 一貫性: 画面内の同種コントロールで表現を統一する。
- 多言語前提: 将来のローカライズを見越し、文字列はリソース化する。
よく使うコントロールの推奨表現例
| UI コントロール | 既定の種別(例) | 推奨 LocalizedControlType(例) | 補足(HelpText 例) |
|---|---|---|---|
| ComboBox | コンボ ボックス | オプション選択メニュー | 一覧から 1 つ選んでください |
| Button | ボタン | 実行 | 押すと設定を保存します |
| ToggleSwitch | トグル | オン/オフ切り替え | オンにすると通知を受け取ります |
| CheckBox | チェック ボックス | 選択項目 | チェックするとメール配信に同意します |
| TextBox | 編集 | 入力欄 | 半角英数字で 8~20 文字 |
WinUI 2 + XAML Islands(C++/WinRT)での実装ポイント
1) 名前空間と参照
UIA 連携には Windows.UI.Xaml.Automation 名前空間が必要です。ビルド環境に WinUI 2 と C++/WinRT を導入済みであれば、上記ヘッダーの #include で利用できます。
2) 生成直後に設定する
AutomationProperties は XAML 要素にアタッチされるため、コントロール生成後すぐに設定します。テンプレート適用後に上書きしても構いませんが、後勝ちである点に注意してください。
3) Name は残す
Name は検索やショートカット、ラベル関連付け(LabeledBy)にも使われます。LocalizedControlType を置き換えても、わかりやすい Name を必ず設定しましょう。
4) HelpText と Required の使い分け
HelpText:操作ヒント・入力ルール・エラーメッセージの要約。IsRequiredForForm:必須入力の明示。Narrator が必要時に読み上げます。
5) 他コントロールへの適用例
Button saveButton;
saveButton.Content(box_value(L"保存"));
AutomationProperties::SetName(saveButton, L"保存");
AutomationProperties::SetLocalizedControlType(saveButton, L"実行");
AutomationProperties::SetHelpText(saveButton, L"押すと設定を保存します");
ToggleSwitch notifySwitch;
notifySwitch.OffContent(box_value(L"通知オフ"));
notifySwitch.OnContent(box_value(L"通知オン"));
AutomationProperties::SetName(notifySwitch, L"通知");
AutomationProperties::SetLocalizedControlType(notifySwitch, L"オン/オフ切り替え");
AutomationProperties::SetHelpText(notifySwitch, L"オンにすると新着をお知らせします");
UI Automation の基礎整理(実務に効く最小限)
- ControlType:論理役割(Button, ComboBox など)。
- LocalizedControlType:上記のローカライズ済み表示名。これを上書きするのが本記事の要点。
- Name:その要素の識別名。基本的にラベル相当。
- HelpText:補足説明。
- LabeledBy:別要素(ラベル)との関連付け。Name を補完。
- AutomationPeer:UIA へプロパティやパターンを公開する仲介クラス。
既定の XAML コントロールは内部の AutomationPeer が UIA プロパティを提供します。LocalizedControlType をアタッチで上書きすると、既定の読み上げ文を望む表現に置換できます。
カスタムコントロールの場合:AutomationPeer をオーバーライド
独自コントロールや独自テンプレートを持つ場合は、対応する AutomationPeer で GetLocalizedControlTypeCore() をオーバーライドします。以下は概念例です。
struct MyComboBox : winrt::Windows::UI::Xaml::Controls::ComboBox
{
using base_type = winrt::Windows::UI::Xaml::Controls::ComboBox;
Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer() override
{
return winrt::make<MyComboBoxAutomationPeer>(*this);
}
};
struct MyComboBoxAutomationPeer
: winrt::Windows::UI::Xaml::Automation::Peers::ComboBoxAutomationPeerT
{
using base_type = winrt::Windows::UI::Xaml::Automation::Peers::ComboBoxAutomationPeerT;
MyComboBoxAutomationPeer(MyComboBox const& owner) : base_type(owner) {}
hstring GetLocalizedControlTypeCore() const override
{
return L"オプション選択メニュー"; // 読み上げ種別を置き換える
}
// 必要に応じて他の Core メソッドも調整
};
可能であればまずは アタッチドプロパティの上書き(= XAML/既定コントロールに対する AutomationProperties::SetLocalizedControlType)から着手し、本当に必要になった場合のみ AutomationPeer の実装へ進むのが保守容易性の面で推奨です。
XAML Islands 特有の留意点(デスクトップホスト)
- ホスト階層: Win32 親ウィンドウに XAML ツリーが埋め込まれます。UIA ツリーでもホスト境界が見えるため、ホスト側に Automation の干渉をしないのが基本です。
- ライフサイクル: Island の生成直後に
AutomationPropertiesを設定する。テンプレート再適用時の再設定も検討する。 - 文字入力言語: 日本語環境では
Language(x:Uidの文化情報)とリソースの整合性に注意。異なる言語混在は読み上げ品質を下げます。 - フォーカス移動: ホスト側のフォーカス手当(TabOrder)を正す。読み上げ順と一致させる。
検証手順:Narrator と開発者ツールで確認する
- Narrator を起動(ショートカット:Win + Ctrl + Enter)。
- 対象コントロールへフォーカスを移動し、読み上げ文を確認。
- 想定どおりでなければ
Name・LocalizedControlType・HelpTextを見直す。 - 開発者向けツール(Inspect など)で UIA プロパティを確認:LocalizedControlType が意図した語になっているか、Name が適切かをチェック。
- 状態(必須/エラー/有効無効)も併せて検証する。
検証観点チェックリスト
| 観点 | 合格条件 | 備考 |
|---|---|---|
| 読み上げの簡潔さ | 10~15 文字程度で要点が伝わる | 冗長な重複を避ける |
| 一貫性 | 同種コントロールで表現が統一 | 画面/アプリ全体で整える |
| 誤解のなさ | 技術用語や略語に依存しない | ユーザーの語彙を前提にしない |
| 補助説明 | HelpText で補足が得られる | 必須・入力規則・操作の説明 |
| 多言語対応 | 文字列がリソース化済み | 日本語以外でも適用可能 |
アンチパターン:やってしまいがちな誤り
- Name にすべて押し込む: 「Options Dropdown(コンボ ボックス)」のように Name に種別を含めると冗長化。UIA の合成規則に任せるか、
LocalizedControlTypeを適切に上書きする。 - LocalizedControlType を空にする: 読み上げが不安定になったり、支援技術間の互換性を損ねることがあります。意味のある短い語に置き換えるのが安全。
- HelpText に操作説明を過剰記述: 長文はユーザー負荷。要点だけ記し、詳細は別 UI(ヘルプ、ツールチップ等)に逃がす。
- 画面内で表現がばらつく: 「メニュー」「リスト」「一覧」など語の揺れは理解を阻害。一貫性を重視。
具体的な改善の流れ(サンプル)
- 要素を洗い出し、既定の読み上げ文(Name + 種別 + 状態)を採取。
- 重複・冗長箇所を特定し、
LocalizedControlTypeの候補語を設計。 Name・LocalizedControlType・HelpTextの 3 点セットを実装。- Narrator で確認し、ユーザーテストで可読性を評価。
- パターン化してコンポーネント化(設計ガイド・ヘルパー API を作成)。
ヘルパー関数で運用コストを下げる
template <typename T>
void ConfigureAccessibleControl(
T const& element,
hstring const& name,
hstring const& localizedType,
hstring const& help,
bool required = false)
{
AutomationProperties::SetName(element, name);
AutomationProperties::SetLocalizedControlType(element, localizedType);
if (!help.empty()) AutomationProperties::SetHelpText(element, help);
AutomationProperties::SetIsRequiredForForm(element, required);
}
この関数をプロジェクト共通ユーティリティとして用意すれば、可読性の高い読み上げをチーム全体で一貫して適用できます。
アクセシビリティとプロダクト価値の両立
一部のユーザーは「ボタン」「チェック ボックス」といった役割語を好みます。LocalizedControlType を置換する際は、用途が伝わる代替語(例:「実行」「選択項目」「オン/オフ切り替え」)を選び、役割のニュアンスを損なわないように注意しましょう。読み上げを単に短くするのではなく、判断と操作に必要な文脈を残すのがコツです。
ケーススタディ:フォームの項目選択を最短で理解させる
プロフィール編集画面の「職種」選択(ComboBox)で、既定の読み上げは「職種 — コンボ ボックス — 折りたたみ」。ユーザーは「何を選ぶのか」は理解できても、技術用語が混ざるため理解の瞬発力が落ちます。以下のように調整すると、意味は変えずに情報密度を上げられます。
AutomationProperties::SetName(jobCombo, L"職種");
AutomationProperties::SetLocalizedControlType(jobCombo, L"オプション選択メニュー");
AutomationProperties::SetHelpText(jobCombo, L"一覧から該当する職種を 1 つ選んでください");
期待される読み上げ:「職種 — オプション選択メニュー」。必要十分で、かつ即時に理解できます。
多言語対応のベストプラクティス
- 文字列はすべてリソース化し、
LocalizedControlTypeも言語ごとに提供する。 - 日英で語の長さが異なる点を踏まえ、過度に長い語を避ける。
- 言語切替時は必ず Narrator で読み上げ確認を行う。
既存画面の棚卸しに使える「短縮方針」サンプル
| 現状(例) | 改善後(例) | 狙い |
|---|---|---|
| 「設定 — ボタン」 | 「設定 — 実行」 | 役割語を用途語へ置換 |
| 「通知 — トグル — オフ」 | 「通知 — オン/オフ切り替え — オフ」 | 「トグル」を避け直感化 |
| 「職種 — コンボ ボックス」 | 「職種 — オプション選択メニュー」 | 技術語の除去 |
| 「メール — 編集」 | 「メール — 入力欄」 | 意味の明確化 |
トラブルシュート
- 上書きが効かない: テンプレート再適用や再生成で値が戻る場合があります。ロード完了後の再設定やバインディングでの適用を検討。
- 読み上げが二重化: Name と LocalizedControlType が同じ語になっていないか確認。
- 一部画面だけ既定語が混入: 共有コンポーネント側で既定値が設定されている可能性。コンポーネントの初期化順を見直す。
- 状態の読み上げ不足: 必須やエラーの状態は
IsRequiredForFormや検証エラーの可視化とセットで。
まとめ
WinUI 2 + C++/WinRT の XAML Islands で Narrator に余計な「コントロール種別」を読ませたくない場合は、AutomationProperties.LocalizedControlType を戦略的に上書きするのがシンプルで確実です。Name と HelpText を組み合わせ、短く・一貫し・誤解のない言葉で UI を説明できれば、キーボード/読み上げ環境の双方で操作効率が向上します。プロダクト全体へパターンとして展開し、検証ループを回していきましょう。
サンプル完全版(抜粋:フォーム片段)
Grid root;
// 職種(コンボボックス)
ComboBox jobCombo;
jobCombo.PlaceholderText(L"選択してください");
jobCombo.Items().Append(box_value(L"エンジニア"));
jobCombo.Items().Append(box_value(L"デザイナー"));
jobCombo.Items().Append(box_value(L"プロダクトマネージャー"));
AutomationProperties::SetName(jobCombo, L"職種");
AutomationProperties::SetLocalizedControlType(jobCombo, L"オプション選択メニュー");
AutomationProperties::SetHelpText(jobCombo, L"一覧から 1 つ選んでください");
AutomationProperties::SetIsRequiredForForm(jobCombo, true);
// 保存(ボタン)
Button saveBtn;
saveBtn.Content(box_value(L"保存"));
AutomationProperties::SetName(saveBtn, L"保存");
AutomationProperties::SetLocalizedControlType(saveBtn, L"実行");
AutomationProperties::SetHelpText(saveBtn, L"押すと入力内容を保存します");
// 通知(トグル)
ToggleSwitch notifySwitch;
notifySwitch.OffContent(box_value(L"通知オフ"));
notifySwitch.OnContent(box_value(L"通知オン"));
AutomationProperties::SetName(notifySwitch, L"通知");
AutomationProperties::SetLocalizedControlType(notifySwitch, L"オン/オフ切り替え");
AutomationProperties::SetHelpText(notifySwitch, L"オンにすると新着をお知らせします");
// ルートへ追加(レイアウトコードは実際の UI に合わせて調整)
root.Children().Append(jobCombo);
root.Children().Append(saveBtn);
root.Children().Append(notifySwitch);
実装チェック用クイックリファレンス
- やること:
SetName/SetLocalizedControlType/SetHelpText/SetIsRequiredForForm - 禁忌: 技術語の残置、重複表現、長文の HelpText、未リソース化
- 検証: Narrator の実読み上げと UIA プロパティを両面で確認

コメント