WPF カスタム コントロールの UI オートメーション
UI オートメーションには、オートメーション クライアントがさまざまなプラットフォームやフレームワークのユーザー インターフェイスを調べたり操作したりするために使用できる、一般化された 1 つのインターフェイスが用意されています。 UI オートメーションを使用すると、品質保証 (テスト) コードと、スクリーン リーダーなどのアクセシビリティ アプリケーションの両方で、ユーザー インターフェイス要素を調べ、他のコードからのユーザー操作をシミュレートできます。 すべてのプラットフォームでの UI オートメーションの詳細については、「アクセシビリティ」を参照してください。
このトピックでは、WPF アプリケーションで実行されるカスタム コントロールのサーバー側 UI オートメーション プロバイダーを実装する方法について説明します。 WPF は、ユーザー インターフェイス要素のツリーを並列化するピア オートメーション オブジェクトのツリーを介して UI オートメーションをサポートします。 アクセシビリティ機能を提供するテスト コードとアプリケーションでは、オートメーション ピア オブジェクトを (インプロセス コード用に) 直接使用することも、UI オートメーションによって提供される一般化されたインターフェイスを介して使用することもできます。
オートメーション ピア クラス
WPF コントロールは、AutomationPeerから派生するピア クラスのツリーを介して UI オートメーションをサポートします。 慣例により、ピア クラス名はコントロール クラス名で始まり、末尾は "AutomationPeer" になります。 たとえば、ButtonAutomationPeer は、Button コントロール クラスのピア クラスです。 ピア クラスは UI オートメーション コントロール型とほぼ同じですが、WPF 要素に固有です。 UI オートメーション インターフェイスを介して WPF アプリケーションにアクセスするオートメーション コードでは、オートメーション ピアは直接使用されませんが、同じプロセス空間内のオートメーション コードではオートメーション ピアを直接使用できます。
組み込みのオートメーション ピア クラス
要素は、ユーザーからのインターフェイス アクティビティを受け入れる場合、またはスクリーン リーダー アプリケーションのユーザーが必要とする情報が含まれている場合に、オートメーション ピア クラスを実装します。 すべての WPF ビジュアル要素にオートメーション ピアがあるわけではありません。 オートメーション ピアを実装するクラスの例としては、Button、TextBox、Labelがあります。 オートメーション ピアを実装しないクラスの例としては、Borderなどの Decoratorから派生するクラスと、Grid や Canvasなどの Panelに基づくクラスがあります。
基底 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から派生する必要があります。
カスタム コントロールを実装する場合は、カスタム コントロールに固有の動作を記述する基本オートメーション ピア クラスの "Core" メソッドをオーバーライドする必要があります。
OnCreateAutomationPeer をオーバーライドする
AutomationPeerから直接または間接的に派生する必要があるプロバイダー オブジェクトを返すように、カスタム コントロールの OnCreateAutomationPeer メソッドをオーバーライドします。
GetPattern をオーバーライドする
オートメーション ピアは、サーバー側 UI オートメーション プロバイダーの実装の一部の側面を簡略化しますが、カスタム コントロール オートメーション ピアはパターン インターフェイスを処理する必要があります。 WPF 以外のプロバイダーと同様に、ピアは、IInvokeProviderなどの System.Windows.Automation.Provider 名前空間内のインターフェイスの実装を提供することで、コントロール パターンをサポートします。 コントロール パターン インターフェイスは、ピア自体または別のオブジェクトによって実装できます。 ピアの GetPattern の実装は、指定されたパターンをサポートするオブジェクトを返します。 UI オートメーション コードは、GetPattern メソッドを呼び出し、PatternInterface 列挙値を指定します。 GetPattern のオーバーライドは、指定したパターンを実装するオブジェクトを返す必要があります。 コントロールにパターンのカスタム実装がない場合は、基本型の GetPattern の実装を呼び出して、その実装を取得するか、パターンがこのコントロール型でサポートされていない場合は null を取得できます。 たとえば、カスタム NumericUpDown コントロールは範囲内の値に設定できるため、UI オートメーション ピアは IRangeValueProvider インターフェイスを実装します。 次の例は、ピアの GetPattern メソッドをオーバーライドして、PatternInterface.RangeValue 値に応答する方法を示しています。
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" メソッドをオーバーライドする
オートメーション コードは、ピア クラスのパブリック メソッドを呼び出すことによって、コントロールに関する情報を取得します。 コントロールに関する情報を提供するには、コントロールの実装が基本オートメーション ピア クラスによって提供されるメソッドと異なる場合に、名前が "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返すことができますが、コントロールが正確に記述されている場合は、より具体的なコントロール型のいずれかを返す必要があります。 ControlType.Custom の戻り値には、プロバイダーが UI オートメーションを実装するための追加の作業が必要です。UI オートメーション クライアント製品は、コントロール構造、キーボード操作、および可能なコントロール パターンを予測できません。
IsContentElementCore メソッドと IsControlElementCore メソッドを実装して、コントロールにデータ コンテンツが含まれているか、ユーザー インターフェイス (またはその両方) で対話型ロールを満たしているかを示します。 既定では、両方のメソッドは true
を返します。 これらの設定により、スクリーン リーダーなどのオートメーション ツールの使いやすさが向上します。これらの方法を使用してオートメーション ツリーをフィルター処理する場合があります。 GetPattern メソッドがパターン処理をサブ要素ピアに転送する場合、サブ要素ピアの IsControlElementCore メソッドは false を返して、サブ要素ピアをオートメーション ツリーから非表示にすることができます。 たとえば、ListBox 内のスクロールは ScrollViewerによって処理され、PatternInterface.Scroll のオートメーション ピアは、ListBoxAutomationPeerに関連付けられている ScrollViewerAutomationPeer の GetPattern メソッドによって返されます。したがって、ScrollViewerAutomationPeer の IsControlElementCore メソッドは false
を返し、ScrollViewerAutomationPeer がオートメーション ツリーに表示されないようにします。
オートメーション ピアは、コントロールに適切な既定値を提供する必要があります。 コントロールを参照する 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 ソースコードを参照してください。
イベントを発生させる
Automation クライアントは、オートメーション イベントをサブスクライブできます。 カスタム コントロールでは、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
関連項目
- UI オートメーションの概要
- Server-Side UI オートメーション プロバイダー実装
- GitHub 上で NumericUpDown カスタムコントロール (C#) を探す
- GitHub にある
NumericUpDown カスタム コントロール (Visual Basic)
.NET Desktop feedback