UI Automation of a Silverlight Custom Control
Microsoft Silverlight will reach end of support after October 2021. Learn more.
Microsoft UI Automation provides a single, generalized interface that automation clients can use to examine or operate the user interfaces of a variety of platforms and frameworks. UI Automation enables both accessibility applications, such as screen readers, and quality-assurance (test) code to examine user-interface elements and simulate user interaction with them from other code. For information about UI Automation across all platforms and in its wider meaning, see Accessibility.
This topic describes how to implement a UI Automation provider for a custom control that runs in a Silverlight application. Silverlight supports UI Automation through a tree of peer automation objects that parallels the tree of user interface elements.
This topic contains the following sections.
- Before You Begin
- Automation Peer Classes
- Built-in Automation Peer Classes
- Peer Navigation
- OnCreateAutomationPeer
- Customizations in a Derived Peer
- Related Topics
Before You Begin
Before you implement an automation peer for a custom control, you should examine or test whether the base control class and its automation peer already provides the accessibility or automation support that you need. In many cases, the combination of the FrameworkElementAutomationPeer implementations, specific peers, and the patterns they implement can provide a satisfactory accessibility experience. Whether this is true depends on to which degree you add object model exposure to your control. Also, this depends on whether your additions to base class functionality correlate to new UI elements in the template contract or to the general visual appearance of the control, and thus introduce new aspects of user experience that require accessibility support.
Automation Peer Classes
Silverlight controls support UI Automation through a tree of peer classes that derive from AutomationPeer. By convention, peer class names begin with the control class name and end with AutomationPeer. For example, ButtonAutomationPeer is the peer class for the Button control class. The peer classes are roughly equivalent to UI Automation control types but are specific to Silverlight elements. Automation code that accesses Silverlight applications through the UI Automation interface does not use automation peers directly, but automation code in the same process space can use automation peers directly. Automation peers must run in a partial-trust environment.
Built-in Automation Peer Classes
Elements implement an automation peer class if they accept interface activity from the user, or if they contain information needed by users of screen-reader applications. Not all Silverlight visual elements have automation peers. Examples of classes that implement automation peers are Button and TextBox. Examples of classes that do not implement automation peers are Border and classes based on Panel, such as Grid and Canvas.
The base Control class does not have a corresponding peer class. If you need a peer class to correspond to a custom control that derives from Control, you should derive the custom peer class from FrameworkElementAutomationPeer.
Peer Navigation
After locating an automation peer, in-process code can navigate the peer tree by calling the object's GetChildren and GetParent methods. Navigation among Silverlight elements within a control is supported by the peer's implementation of the GetChildrenCore method. The UI Automation system calls this method to build up a tree of subelements contained within a control; for example, list items in a list box. The default GetChildrenCore method in FrameworkElementAutomationPeer traverses the visual tree of elements to build the tree of automation peers. Custom controls override this method to expose children elements to automation clients, returning the automation peers of elements that convey information or allow user interaction.
OnCreateAutomationPeer
Override the OnCreateAutomationPeer method for your custom control so that it returns your provider object, which must derive directly or indirectly from AutomationPeer.
For example, the following code declares that the custom control NumericUpDown should use the peer NumericUpDownPeer for UI Automation purposes:
using System.Windows.Automation.Peers;
...
public class NumericUpDown : Selector {
public NumericUpDown() {
// other initialization; DefaultStyleKey etc.
}
...
protected override AutomationPeer OnCreateAutomationPeer()
{
return new NumericUpDownAutomationPeer(this);
}
}
Customizations in a Derived Peer
All classes that derive from UIElement contain the protected virtual method OnCreateAutomationPeer. Silverlight calls OnCreateAutomationPeer to get the automation peer object for each control. Automation code can use the peer to get information about a control’s characteristics and features and to simulate interactive use. A custom control that supports automation must override OnCreateAutomationPeer and return an instance of a class that derives from AutomationPeer. For example, if a custom control derives from the ButtonBase class, then the object returned by OnCreateAutomationPeer should derive from ButtonBaseAutomationPeer.
The automation peer should define a type-safe constructor that uses an instance of the owner control for base initialization. The following is a typical example, where the only implementation is to pass the owner on to the SelectorAutomationPeer base (and ultimately it is the FrameworkElementAutomationPeer that actually uses owner to set FrameworkElementAutomationPeer.Owner).
public NumericUpDownAutomationPeer(NumericUpDown owner): base(owner)
{}
In typical usage within OnCreateAutomationPeer, the owner is specified as this because it is in the same scope as the rest of the control class definition.
When implementing a custom control, you should override any of the "Core" methods from the base automation peer class that describe behavior unique and specific to your custom control.
Override GetPattern
Automation peers simplify some implementation aspects of UI Automation providers, but custom control automation peers must still handle pattern interfaces. Like non-Silverlight providers, peers support control patterns by providing implementations of interfaces in the System.Windows.Automation.Provider namespace, such as IInvokeProvider. The control pattern interfaces can be implemented by the peer itself or by another object. The peer's implementation of GetPattern returns the object that supports the specified pattern. UI Automation code calls the GetPattern method and specifies a PatternInterface enumeration value. Your override of GetPattern should return the object that implements the specified pattern. If your control does not have a custom implementation of a pattern, you can call the base type's implementation of GetPattern to retrieve either its implementation or null if the pattern is not supported for this control type. For example, a custom numeric up-down control can be set to a value within a range, so its UI Automation peer would implement the IRangeValueProvider interface. The following example shows how the peer's GetPattern method is overridden to respond to a PatternInterface.RangeValue value.
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
public override object GetPattern(PatternInterface patternInterface)
{
if (patternInterface == PatternInterface.RangeValue)
{
return this;
}
return base.GetPattern(patternInterface);
}
A GetPattern method can also specify a subelement as a pattern provider. The following code shows how ItemsControl transfers scroll pattern handling to the peer of its internal ScrollViewer control.
public override object GetPattern(PatternInterface patternInterface)
{
if (patternInterface == PatternInterface.Scroll)
{
ItemsControl owner = (ItemsControl) base.Owner;
UIElement itemsHost = owner.ItemsHost;
ScrollViewer element = null;
while (itemsHost != owner)
{
itemsHost = VisualTreeHelper.GetParent(itemsHost) as UIElement;
element = itemsHost as ScrollViewer;
if (element != null)
{
break;
}
}
if (element != null)
{
AutomationPeer peer = FrameworkElementAutomationPeer.CreatePeerForElement(element);
if ((peer != null) && (peer is IScrollProvider))
{
return (IScrollProvider) peer;
}
}
}
return base.GetPattern(patternInterface);
}
To specify a subelement for pattern handling, this code gets the subelement object, creates a peer by using the CreatePeerForElement method, and returns the new peer.
For a list of the provider patterns that are available in the Silverlight implementation of UI Automation support, see System.Windows.Automation.Provider.
Override "Core" Methods
Automation code gets information about your control by calling public methods of the peer class. To provide information about your control, override each method whose name ends with "Core" when your control implementation differs from that of that provided by the base automation peer class. At a minimum, your control's automation peer should implement the GetClassNameCore and GetAutomationControlTypeCore methods, as shown in the following example.
Protected Overrides Function GetClassNameCore() As String
Return "NumericUpDown"
End Function
Protected Overrides Function GetAutomationControlTypeCore() As AutomationControlType
Return AutomationControlType.Spinner
End Function
protected override string GetClassNameCore()
{
return "NumericUpDown";
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Spinner;
}
Your implementation of GetAutomationControlTypeCore describes your control by returning a AutomationControlType value. Although you can return AutomationControlType.Custom, you should return one of the more specific control types if it accurately describes your control. A return value of AutomationControlType.Custom requires extra work for the provider to implement UI Automation, and UI Automation client products are unable to anticipate the control structure, keyboard interaction, and possible control patterns.
Implement the IsContentElementCore and IsControlElementCore methods to indicate whether your control contains data content or fulfills an interactive role in the user interface (or both). By default, both methods return true. These settings improve the usability of automation tools such as screen readers, which may use these methods to filter the automation tree. If your GetPattern method transfers pattern handling to a subelement peer, the subelement peer's IsControlElementCore method can return false to hide the subelement peer from the automation tree.
Your automation peer should provide appropriate default values for your control. Note that XAML that references your control can override your peer implementations of core methods by including AutomationProperties attributes. For example, the following XAML creates a button that has two customized UI Automation properties.
<Button AutomationProperties.Name="Special"
AutomationProperties.HelpText="This is a special button."/>
Implement Pattern Providers
The interfaces implemented by a custom provider are explicitly declared if the owning element derives directly from Control. For example, the following code declares a peer for a Control that implements a range value.
public class RangePeer1 : FrameworkElementAutomationPeer, IRangeValueProvider { }
For a list of the provider patterns that are available in the Silverlight implementation of UI Automation support, see System.Windows.Automation.Provider.
Raise Events
Automation clients can subscribe to automation events. Custom controls must report changes to control state by calling the RaiseAutomationEvent method. Similarly, when a property value changes, call the RaisePropertyChangedEvent method. The following code shows how to get the peer object from within the control code and call a method to raise an event. As an optimization, the code determines if there are any listeners for this event type. Raising the event only when there are listeners avoids unnecessary overhead and helps the control remain responsive.
If AutomationPeer.ListenerExists(AutomationEvents.PropertyChanged) Then
Dim peer As NumericUpDownAutomationPeer
peer = CType(FrameworkElementAutomationPeer.FromElement(nudCtrl), NumericUpDownAutomationPeer)
If (Not (peer) Is Nothing) Then
peer.RaisePropertyChangedEvent(RangeValuePatternIdentifiers.ValueProperty, CType(oldValue, Double), CType(newValue, Double))
End If
End If
if (AutomationPeer.ListenerExists(AutomationEvents.PropertyChanged))
{
NumericUpDownAutomationPeer peer =
FrameworkElementAutomationPeer.FromElement(nudCtrl) as NumericUpDownAutomationPeer;
if (peer != null)
{
peer.RaisePropertyChangedEvent(
RangeValuePatternIdentifiers.ValueProperty,
(double)oldValue,
(double)newValue);
}
}