Server-Side UI オートメーション プロバイダーを実装する

このトピックでは、C++ で記述されたカスタム コントロール用のサーバー側の Microsoft UI オートメーション プロバイダーを実装する方法について説明します。 次のセクションが含まれます。

サーバー側プロバイダーを実装する方法を示すコード例については、「UI オートメーション プロバイダーの方法に関するトピック」を参照してください。

プロバイダー ツリー構造

UIA クライアントからアクセスできる必要がある UI 要素ごとに UIA プロバイダーを実装する必要があります。

たとえば、各要素は IRawElementProviderFragment を 実装する必要があり、アプリケーションのルート要素は IRawElementProviderFragmentRoot を実装する必要があります。 さらに、各プロバイダー要素は次にリンクする必要があります。

  • parent
  • 以前のプロバイダー要素
  • next provider 要素
  • 最初のプロバイダーの子
  • 最後のプロバイダーの子

プロバイダーのインターフェイス

次のコンポーネント オブジェクト モデル (COM) インターフェイスは、カスタム コントロールの機能を提供します。 基本的な機能を提供するには、すべてのUI オートメーション プロバイダーが少なくとも IRawElementProviderSimple インターフェイスを実装する必要があります。 IRawElementProviderFragment インターフェイスと IRawElementProviderFragmentRoot インターフェイスは省略可能ですが、追加の機能を提供するには、複雑なコントロール内の要素に実装する必要があります。

インターフェイス 説明
IRawElementProviderSimple コントロール パターンとプロパティのサポートなど、ウィンドウでホストされるコントロールの基本的な機能を提供します。
IRawElementProviderFragment フラグメント内の移動、フォーカスの設定、要素の外接する四角形の返しなど、複雑なコントロール内の要素の機能を追加します。
IRawElementProviderFragmentRoot 指定した座標での子要素の検索やコントロール全体のフォーカス状態の設定などを含む、複雑なコントロールのルート要素の機能を追加します。

 

Note

マネージド コード用のUI オートメーション API では、これらのインターフェイスが継承階層を形成します。 これは、インターフェイスが完全に分離されている C++ では当てはまりません。

 

次のインターフェイスは追加の機能を提供しますが、実装は省略可能です。

インターフェイス 説明
IRawElementProviderAdviseEvents プロバイダーがイベントの要求を追跡できるようにします。
IRawElementProviderHwndOverride フラグメントのUI オートメーション ツリー内のウィンドウ ベースの要素の位置を変更できるようにします。

 

UI オートメーション プロバイダーに必要な機能

UI オートメーションと通信するには、次の表で説明する機能のメイン領域をコントロールで実装する必要があります。

機能 実装
プロバイダーをUI オートメーションに公開します。 コントロール ウィンドウに送信された WM_GETOBJECT メッセージに応答して、 IRawElementProviderSimple を実装する オブジェクトを返します。 フラグメントの場合、これはフラグメント ルートのプロバイダーである必要があります。
プロパティ値を指定します。 IRawElementProviderSimple::GetPropertyValue を実装して、値を指定またはオーバーライドします。
クライアントがコントロールと対話できるようにします。 IInvokeProvider など、適切な各コントロール パターンをサポートするインターフェイスを実装します。 IRawElementProviderSimple::GetPatternProvider の実装で、これらのコントロール パターン プロバイダーを返します。
イベントを発生させます。 UiaRaiseAutomationEventIProxyProviderWinEventSink のメソッド。
フラグメント内のナビゲーションとフォーカスを有効にします。 フラグメント内の各要素に IRawElementProviderFragment を実装します。 フラグメントの一部ではない要素には必要ありません。
フラグメント内の子要素のフォーカスと検索を有効にします。 IRawElementProviderFragmentRoot を実装します。 フラグメント ルートではない要素には必要ありません。

 

プロパティ値

カスタム コントロールのUI オートメーション プロバイダーは、UI オートメーションおよびクライアント アプリケーションで使用できる特定のプロパティをサポートする必要があります。 windows でホストされている要素の場合、UI オートメーションは既定のウィンドウ プロバイダーからいくつかのプロパティを取得できますが、カスタム プロバイダーから他のプロパティを取得する必要があります。

通常、ウィンドウ ベースのコントロールのプロバイダーは、 PROPERTYID で識別される次のプロパティを指定する必要はありません。

ウィンドウでホストされている単純な要素またはフラグメント ルートの RuntimeId プロパティは、ウィンドウから取得されます。 ただし、ルートの下のフラグメント要素 (リスト ボックス内のリスト アイテムなど) は、独自の識別子を提供する必要があります。 詳細については、「 IRawElementProviderFragment::GetRuntimeId」を参照してください

isKeyboardFocusable プロパティは、Windows フォーム コントロールでホストされているプロバイダーに対して返す必要があります。 この場合、ウィンドウの既定のプロバイダーは適切な値を取得できないことがあります。

Name プロパティは通常、ホスト プロバイダーによって提供されます。

プロバイダーからのイベント

UI オートメーション プロバイダーは、イベントを発生させて、UI の状態の変更をクライアント アプリケーションに通知する必要があります。 イベントを発生させるために、次の関数を使用します。

機能 説明
UiaRaiseAutomationEvent コントロール パターンによってトリガーされるイベントを含む、さまざまなイベントを発生させます。
UiaRaiseAutomationPropertyChangedEvent UI オートメーション プロパティが変更された場合にイベントを発生させます。
UiaRaiseStructureChangedEvent 要素を削除または追加するなどして、UI オートメーション ツリーの構造が変更されたときにイベントを発生させます。

 

イベントの目的は、UI で何かが行われたことをクライアントに通知することです。 プロバイダーは、変更がユーザー入力によってトリガーされたか、UI オートメーションを使用するクライアント アプリケーションによってトリガーされたかに関係なく、イベントを発生させる必要があります。 たとえば、 UIA_Invoke_InvokedEventIdによって識別 されるイベントは、直接ユーザー入力または IUIAutomationInvokePattern::Invoke を呼び出すクライアント アプリケーションによって、コントロールが呼び出されるたびに発生する必要があります。

パフォーマンスを最適化するため、プロバイダーは選択的にイベントを発生させたり、イベントを受け取るクライアント アプリケーションが登録されていないときにはイベントを発生させないようにすることができます。 最適化には、次の API 要素が使用されます。

API 要素 説明
UiaClientsAreListening この関数は、クライアント アプリケーションがUI オートメーション イベントをサブスクライブしているかどうかを確認します。
IRawElementProviderAdviseEvents フラグメント ルートにこのインターフェイスを実装すると、クライアントがフラグメント上のイベントのイベント ハンドラーを登録および登録解除するときに、プロバイダーに通知を受け取る必要があります。

 

Note

COM プログラミングでの参照カウントの実装と同様に、UI オートメーション プロバイダーは IUnknown インターフェイスの IUnknown::AddRef メソッドや Release メソッドのような IRawElementProviderAdviseEvents::AdviseEventAdded メソッドと AdviseEventRemoved メソッドを処理することが重要です。 特定のイベントまたはプロパティに対して AdviseEventRemoved よりも AdviseEventAdded が呼び出 された 回数が多い限り、プロバイダーは引き続き対応するイベントを発生させる必要があります。一部のクライアントはまだリッスンしているためです。 または、UI オートメーション プロバイダーは、UiaClientsAreListening 関数を使用して、少なくとも 1 つのクライアントがリッスンしているかどうかを判断し、リッスンしている場合は、すべての適切なイベントを発生させることができます。

 

プロバイダー ナビゲーション

ウィンドウでホストされるカスタム ボタンなど、単純なコントロールのプロバイダーは、UI オートメーション ツリーでのナビゲーションをサポートする必要はありません。 要素との間のナビゲーションは、 IRawElementProviderSimple::HostRawElementProvider の実装で指定されているホスト ウィンドウの既定のプロバイダーによって処理されます。 ただし、複雑なカスタム コントロール用のプロバイダーを実装する場合は、フラグメントのルート ノードと子孫ノード、および兄弟ノード間のナビゲーションをサポートする必要があります。

Note

ルート以外のフラグメントの要素は、HostRawElementProvider から NULL を返す必要があります。これは、ウィンドウで直接ホストされておらず、既定のプロバイダーがそれらの間のナビゲーションをサポートできないためです。

 

フラグメントの構造は、 IRawElementProviderFragment::Navigate の実装によって決まります。 このメソッドは、各フラグメントから可能なそれぞれの方向に対して、その方向の要素に対するプロバイダー オブジェクトを返します。 その方向に要素がない場合、メソッドは NULL を返します。

フラグメント ルートは、子要素へのナビゲーションのみをサポートします。 たとえば、方向が NavigateDirection_FirstChildの場合、リスト ボックスはリスト内の最初の項目を返し、方向が NavigateDirection_LastChild場合は最後の項目を返します。 フラグメント ルートでは、親または兄弟へのナビゲーションはサポートされていません。これはホスト ウィンドウ プロバイダーによって処理されます。

ルート以外のフラグメントの要素は、親へのナビゲーションと、(存在する場合は) 兄弟や子へのナビゲーションをサポートする必要があります。

新しい親の割り当て

ポップアップ ウィンドウは実際には最上位レベルのウィンドウであり、既定では、デスクトップの子としてUI オートメーション ツリーに表示されます。 ただし、多くの場合、ポップアップ ウィンドウは論理的には他のコントロールの子になります。 たとえば、コンボ ボックスのドロップダウン リストは、論理的にはコンボ ボックスの子です。 同様に、メニューのポップアップ ウィンドウは、論理的にはメニューの子になります。 UI オートメーションは、新しい親をポップアップ ウィンドウに割り当てて、関連付けられたコントロールの子と見なされるようにするためのサポートを提供します。

新しい親をポップアップ ウィンドウに割り当てるには:

  1. ポップアップ ウィンドウ用のプロバイダーを作成します。 そのためには、ポップアップ ウィンドウの クラスを事前に認識しておく必要があります。
  2. すべてのプロパティとコントロール パターンを、それ自体のコントロールであるかのように、そのポップアップに対して通常どおり実装します。
  3. UiaHostProviderFromHwnd から取得した値を返すように、IRawElementProviderSimple::HostRawElementProvider プロパティを実装します。ここで、 パラメーターはポップアップ ウィンドウのウィンドウ ハンドルです。
  4. IRawElementProviderFragment::Navigate をポップアップ ウィンドウとその親に実装して、論理親から論理子、および兄弟子間のナビゲーションが適切に処理されるようにします。

UI オートメーションは、ポップアップ ウィンドウを検出すると、既定のナビゲーションがオーバーライドされていると認識し、デスクトップの子として検出されたポップアップ ウィンドウをスキップします。 代わりに、ノードはフラグメント経由でのみ到達可能です。

新しい親を割り当てることは、コントロールが任意のクラスのウィンドウをホストできる場合には適していません。 たとえば、Rebar コントロールはバンド内の任意の種類のウィンドウをホストできます。 このような場合に対処するために、UI オートメーションでは、次のセクションで説明するように、別の形式のウィンドウ再配置がサポートされています。

プロバイダーの位置変更

UI オートメーションフラグメントには、それぞれウィンドウに含まれる 2 つ以上の要素を含めることができます。 各ウィンドウには、ウィンドウを含むウィンドウの子と見なされる独自の既定のプロバイダーがあるため、既定では、UI オートメーション ツリーは、フラグメント内のウィンドウを親ウィンドウの子として表示します。 ほとんどの場合、これは適切な動作ですが、場合によっては UI の論理構造が一致しないために混乱を招く可能性があります。

良い例が Rebar コントロールです。 Rebar コントロールにはバンドが含まれており、それぞれのバンドには、ツール バー、編集ボックス、コンボ ボックスなどのウィンドウ ベースのコントロールを含めることができます。 Rebar ウィンドウの既定のウィンドウ プロバイダーでは、バンド コントロール ウィンドウが子として表示され、Rebar プロバイダーはバンドを子として表示します。 ウィンドウ プロバイダーと Rebar プロバイダーは連携して作業し、子を組み合わせているため、バンドとウィンドウ ベースのコントロールの両方が、Rebar コントロールの子として表示されます。 ただし、論理的には、バンドのみが Rebar コントロールの子として表示され、各バンド プロバイダーは、含まれているコントロールの既定のウィンドウ プロバイダーと結合する必要があります。

これを実現するために、Rebar コントロールのフラグメント ルート プロバイダーは、バンドを表す一連の子を公開します。 各バンドには、プロパティとコントロール パターンを公開できる 1 つのプロバイダーがあります。 IRawElementProviderSimple::HostRawElementProvider の実装では、バンド プロバイダーはコントロール ウィンドウの既定のウィンドウ プロバイダーを返します。このプロバイダーは、UiaHostProviderFromHwnd を呼び出して取得し、コントロールのウィンドウ ハンドル (HWND) を渡します。 最後に、Rebar のフラグメント ルート プロバイダーは IRawElementProviderHwndOverride インターフェイスを実装し、 IRawElementProviderHwndOverride::GetOverrideProviderForHwnd の実装では、指定されたウィンドウに含まれるコントロールの適切なバンド プロバイダーを返します。

プロバイダーの切断

通常、アプリケーションは必要に応じてコントロールを作成し、後で破棄します。 コントロールを破棄した後、UiaDisconnectProvider を呼び出して、コントロールに関連付けられているUI オートメーション プロバイダー リソースを解放する必要があります。

同様に、アプリケーションでは、UiaDisconnectAllProviders 関数を使用して、シャットダウンする前に、アプリケーション内のすべてのプロバイダーによって保持されているすべてのUI オートメーション リソースを解放する必要があります。

UI オートメーション プロバイダー プログラマー ガイド