WPF カスタム コントロールの UI オートメーション

UI オートメーションは、オートメーション クライアントが使用してさまざまなプラットフォームやフレームワークのユーザー インターフェイスを調査または操作できる、単一の汎用的なインターフェイスを提供します。 UI オートメーションでは、品質保証 (テスト) のコードとスクリーン リーダーなどのユーザー補助アプリケーションの両方を使用して、ユーザー インターフェイス要素を調べて、他のコードからその要素を使用してユーザーの操作をシミュレーションできます。 すべてのプラットフォームにまたがる UI オートメーションについて詳しくは、「ユーザー補助機能」を参照してください。

このトピックでは、WPF アプリケーションで動作するカスタム コントロールのサーバー側 UI オートメーション プロバイダーを実装する方法について説明します。 WPF は、ユーザー インターフェイス要素のツリーに対応するピア オートメーション オブジェクトのツリーを使用して UI オートメーションをサポートします。 テスト コードおよびユーザー補助機能を提供するアプリケーションは、オートメーション ピア オブジェクトを直接 (プロセス内コードに対して)、または UI オートメーションが提供する汎用のインターフェイスを介して使用できます。

オートメーション ピア クラス

WPF コントロールでは、AutomationPeer から派生するピア クラスのツリーを通じて UI オートメーションがサポートされます。 規則上、ピア クラス名は、コントロールのクラス名で始まり、"AutomationPeer" で終わります。 たとえばButtonAutomationPeerButton コントロール クラスのピア クラスです。 ピア クラスは、UI オートメーション コントロール型とほぼ同等ですが、WPF 要素に固有のものです。 UI オートメーション インターフェイスを介して WPF アプリケーションにアクセスするオートメーション コードは、オートメーション ピアを直接使用しませんが、同じプロセス空間のオートメーション コードは、オートメーション ピアを直接使用できます。

組み込みのオートメーション ピア クラス

要素は、ユーザーからのインターフェイス アクティビティを受け入れる場合、またはスクリーン リーダー アプリケーションのユーザーが必要とする情報が含まれている場合、オートメーション ピア クラスを実装します。 すべての WPF のビジュアル要素にオートメーション ピアがあるわけではありません。 オートメーション ピアが実装されているクラスの例は、ButtonTextBoxLabel などです。 オートメーション ピアが実装されていないクラスの例は、Decorator から派生されたクラス (Border など)、および Panel に基づくクラス (GridCanvas など) です。

基底クラス Control には、対応するピア クラスはありません。 Control から派生したカスタム コントロールにピア クラスを対応させる必要がある場合は、カスタム ピア クラスを FrameworkElementAutomationPeer から派生させる必要があります。

派生ピアのセキュリティに関する考慮事項

オートメーション ピアは、部分信頼環境で実行する必要があります。 UIAutomationClient アセンブリ内のコードは部分信頼環境で動作するように構成されていないため、オートメーション ピア コードでそのアセンブリを参照しないでください。 代わりに、UIAutomationTypes アセンブリのクラスを使用する必要があります。 たとえば、UIAutomationTypes アセンブリの AutomationElementIdentifiers クラスを使用する必要があります。これは、UIAutomationClient アセンブリの AutomationElement に対応します。 オートメーション ピア コードで UIAutomationTypes アセンブリを参照すると安全です。

ピア ナビゲーション

オートメーション ピアを見つけた後、オブジェクトの GetChildren メソッドおよび GetParent メソッドを呼び出すことによって、プロセス内コードでピア ツリー内を移動できます。 コントロール内の WPF 要素間の移動は、ピアでの GetChildrenCore メソッドの実装によってサポートされています。 UI オートメーション システムは、このメソッドを呼び出して、コントロール内に含まれるサブ要素のツリーを構築します。たとえば、リスト ボックス内の項目を一覧表示します。 既定の UIElementAutomationPeer.GetChildrenCore メソッドでは、要素のビジュアル ツリーが走査されて、オートメーション ピアのツリーが構築されます。 カスタム コントロールは、このメソッドをオーバーライドして子要素をオートメーション クライアントに公開し、情報を伝達するまたはユーザーによる操作を許可する要素のオートメーション ピアを返します。

派生ピアのカスタマイズ

UIElement および ContentElement から派生したすべてのクラスには、保護された仮想メソッド OnCreateAutomationPeer が含まれています。 WPF では、OnCreateAutomationPeer が呼び出されて、各コントロールのオートメーション ピア オブジェクトが取得されます。 オートメーション コードは、ピアを使用して、コントロールの特性と機能に関する情報を取得し、対話型の使用をシミュレートします。 オートメーションをサポートするカスタム コントロールでは、OnCreateAutomationPeer をオーバーライドし、AutomationPeer の派生クラスのインスタンスを返す必要があります。 たとえば、カスタム コントロールが ButtonBase クラスから派生する場合、OnCreateAutomationPeer によって返されるオブジェクトは、ButtonBaseAutomationPeer から派生している必要があります。

カスタム コントロールを実装する場合、カスタム コントロールに固有の動作を説明する基本オートメーション ピア クラスからの「コア」メソッドをオーバーライドする必要があります。

OnCreateAutomationPeer のオーバーライド

カスタム コントロールの OnCreateAutomationPeer メソッドをオーバーライドし、AutomationPeer から直接または間接に派生する必要があるプロバイダー オブジェクトを返すようにします。

GetPattern のオーバーライド

オートメーション ピアは、サーバー側の UI オートメーション プロバイダーの実装の一部を簡略化しますが、カスタム コントロール オートメーション ピアでもパターン インターフェイスを処理する必要があります。 WPF 以外のプロバイダーと同様に、ピアでは、System.Windows.Automation.Provider 名前空間内のインターフェイスの実装を提供することによって、コントロール パターンをサポートします (IInvokeProvider など)。 コントロール パターンのインターフェイスは、ピア自体または別のオブジェクトによって実装できます。 GetPattern のピアでの実装では、指定されたパターンをサポートするオブジェクトが返されます。 UI オートメーションのコードでは、GetPattern メソッドが呼び出されて、PatternInterface 列挙値が指定されます。 GetPattern のオーバーライドでは、指定されたパターンが実装されているオブジェクトを返す必要があります。 コントロールにパターンのカスタム実装がない場合、GetPattern の基本型の実装を呼び出し、その実装を取得できます。パターンがこのコントロール型でサポートされていない場合は null が取得されます。 たとえば、カスタムの NumericUpDown コントロールを範囲内の値に設定できるため、その UI オートメーション ピアでは IRangeValueProvider インターフェイスを実装します。 次の例では、PatternInterface.RangeValue の値に応答するようにピアの GetPattern メソッドをオーバーライドする方法を示します。

public override object GetPattern(PatternInterface patternInterface)
{
    if (patternInterface == PatternInterface.RangeValue)
    {
        return this;
    }
    return base.GetPattern(patternInterface);
}
Public Overrides Function GetPattern(ByVal patternInterface As PatternInterface) As Object
    If patternInterface = PatternInterface.RangeValue Then
        Return Me
    End If
    Return MyBase.GetPattern(patternInterface)
End Function

GetPattern メソッドでは、パターン プロバイダーとしてサブ要素を指定することもできます。 次のコードでは、ItemsControl においてスクロール パターンの処理を内部の ScrollViewer コントロールのピアに転送する方法を示します。

public override object GetPattern(PatternInterface patternInterface)  
{  
    if (patternInterface == PatternInterface.Scroll)  
    {  
        ItemsControl owner = (ItemsControl) base.Owner;  
  
        // ScrollHost is internal to the ItemsControl class  
        if (owner.ScrollHost != null)  
        {  
            AutomationPeer peer = UIElementAutomationPeer.CreatePeerForElement(owner.ScrollHost);  
            if ((peer != null) && (peer is IScrollProvider))  
            {  
                peer.EventsSource = this;  
                return (IScrollProvider) peer;  
            }  
        }  
    }  
    return base.GetPattern(patternInterface);  
}  
Public Class Class1  
    Public Overrides Function GetPattern(ByVal patternInterface__1 As PatternInterface) As Object  
        If patternInterface1 = PatternInterface.Scroll Then  
            Dim owner As ItemsControl = DirectCast(MyBase.Owner, ItemsControl)  
  
            ' ScrollHost is internal to the ItemsControl class  
            If owner.ScrollHost IsNot Nothing Then  
                Dim peer As AutomationPeer = UIElementAutomationPeer.CreatePeerForElement(owner.ScrollHost)  
                If (peer IsNot Nothing) AndAlso (TypeOf peer Is IScrollProvider) Then  
                    peer.EventsSource = Me  
                    Return DirectCast(peer, IScrollProvider)  
                End If  
            End If  
        End If  
        Return MyBase.GetPattern(patternInterface1)  
    End Function  
End Class  

パターン処理に対してサブ要素を指定するため、このコードでは、サブ要素オブジェクトを取得し、CreatePeerForElement メソッドを使用してピアを作成し、新しいピアの EventsSource プロパティに現在のピアを設定して、新しいピアを返しています。 サブ要素で EventsSource を設定すると、そのサブ要素がオートメーション ピア ツリーに表示されなくなり、そのサブ要素によって生成されるすべてのイベントを EventsSource で指定されているコントロールから発信されたようにすることが指定されます。 ScrollViewer コントロールはオートメーション ツリーに表示されず、それによって生成されるスクロール イベントは ItemsControl オブジェクトから発信されたように見えます。

「コア」メソッドのオーバーライド

オートメーション コードは、ピア クラスのパブリック メソッドを呼び出して、コントロールに関する情報を取得します。 コントロールに関する情報を提供するには、コントロールの実装が基本オートメーション ピア クラスで提供されているものとは異なる場合に、"Core" で終わる名前を持つ各メソッドをオーバーライドします。 少なくとも、コントロールでは、次の例で示すように、GetClassNameCore メソッドと GetAutomationControlTypeCore メソッドを実装する必要があります。

protected override string GetClassNameCore()
{
    return "NumericUpDown";
}

protected override AutomationControlType GetAutomationControlTypeCore()
{
    return AutomationControlType.Spinner;
}
Protected Overrides Function GetClassNameCore() As String
    Return "NumericUpDown"
End Function

Protected Overrides Function GetAutomationControlTypeCore() As AutomationControlType
    Return AutomationControlType.Spinner
End Function

GetAutomationControlTypeCore の実装では、ControlType の値を返すことによって、コントロールを説明します。 ControlType.Custom を返してもかまいませんが、より具体的なコントロール型の 1 つを返すことによってコントロールが正確に説明される場合は、そうする必要があります。 ControlType.Custom の戻り値では、プロバイダーで UI オートメーションを実装するための追加作業が必要です。また、UI オートメーションのクライアント製品では、コントロールの構造、キーボード操作、および可能なコントロール パターンを予測することはできません。

コントロールにデータの内容が含まれるか、またはコントロールのユーザー インターフェイスで対話型のロールを果たすか (またはその両方か) を示すように、IsContentElementCore メソッドと IsControlElementCore メソッドを実装します。 既定では、両方のメソッドが true を返します。 これらの設定は、オートメーション ツリーをフィルター処理するこれらのメソッドを使用できるスクリーン リーダーなどのオートメーション ツールの使いやすさを向上します。 GetPattern メソッドでパターン処理をサブ要素ピアに転送した場合は、サブ要素ピアの IsControlElementCore メソッドで、false を返し、オートメーション ツリーにサブ要素ピアが表示されないようにできます。 たとえば、ListBox でのスクロールは ScrollViewer によって処理され、ListBoxAutomationPeer に関連付けられている ScrollViewerAutomationPeerGetPattern メソッドでは PatternInterface.Scroll のオートメーション ピアが返されます。したがって、ScrollViewerAutomationPeer はがートメーション ツリーに表示されないように、ScrollViewerAutomationPeerIsControlElementCore メソッドでは false を返します。

オートメーション ピアは、コントロールに適切な既定値を提供する必要があります。 コントロールを参照する XAML では、AutomationProperties 属性を含めることによって、コア メソッドのピアによる実装をオーバーライドできることに注意してください。 たとえば、次の XAML では、カスタマイズした 2 つの UI オートメーション プロパティを使うボタンを作ります。

<Button AutomationProperties.Name="Special"
    AutomationProperties.HelpText="This is a special button."/>  

パターン プロバイダーの実装

カスタム プロバイダーによって実装されるインターフェイスは、所有している要素が Control から直接派生している場合は、明示的に宣言されます。 たとえば、次のコードでは、範囲の値を実装する Control のピアが宣言されています。

public class RangePeer1 : FrameworkElementAutomationPeer, IRangeValueProvider { }  
Public Class RangePeer1  
    Inherits FrameworkElementAutomationPeer  
    Implements IRangeValueProvider  
End Class  

所有しているコントロールが RangeBase などの特定の型のコントロールから派生している場合、同等の派生ピア クラスからピアを派生できます。 この場合、ピアの派生元の RangeBaseAutomationPeer によって、IRangeValueProvider の基本的な実装が提供されます。 次のコードは、そのようなピアの宣言を示します。

public class RangePeer2 : RangeBaseAutomationPeer { }  
Public Class RangePeer2  
    Inherits RangeBaseAutomationPeer  
End Class  

実装の例については、NumericUpDown カスタム コントロールが実装および使用されている C# または Visual Basic のソース コードを参照してください。

イベントを発生させる

オートメーション クライアントは、オートメーション イベントをサブスクライブできます。 カスタム コントロールでは、RaiseAutomationEvent メソッドを呼び出すことによって、コントロールの状態に対する変更を報告する必要があります。 同様に、プロパティの値が変更されたときは、RaisePropertyChangedEvent メソッドを呼び出します。 次のコードは、コントロール コード内からピア オブジェクトを取得し、イベントを発生させるメソッドを呼び出す方法を示しています。 最適化として、このコードは、このイベント型のリスナーがいるかどうかを判断します。 リスナーがいる場合にのみイベントを発生させると、不要なオーバーヘッドを回避し、コントロールの応答性を維持できます。

if (AutomationPeer.ListenerExists(AutomationEvents.PropertyChanged))
{
    NumericUpDownAutomationPeer peer =
        UIElementAutomationPeer.FromElement(nudCtrl) as NumericUpDownAutomationPeer;

    if (peer != null)
    {
        peer.RaisePropertyChangedEvent(
            RangeValuePatternIdentifiers.ValueProperty,
            (double)oldValue,
            (double)newValue);
    }
}
If AutomationPeer.ListenerExists(AutomationEvents.PropertyChanged) Then
    Dim peer As NumericUpDownAutomationPeer = TryCast(UIElementAutomationPeer.FromElement(nudCtrl), NumericUpDownAutomationPeer)

    If peer IsNot Nothing Then
        peer.RaisePropertyChangedEvent(RangeValuePatternIdentifiers.ValueProperty, CDbl(oldValue), CDbl(newValue))
    End If
End If

関連項目