Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
This article explains automation peers in Microsoft UI Automation and how to provide robust automation support for custom UI classes.
Note
This guidance uses WinUI 3 / Windows App SDK namespaces and references based on Microsoft.UI.Xaml.Automation.Peers.AutomationPeer.
UI Automation provides a framework used by automation clients to inspect and interact with UI across platforms. In Windows apps, most built-in controls already provide UI Automation support. When you derive a new control or support type from existing non-sealed classes, you may introduce behavior not represented by the default peer. In that case, extend automation support by deriving from the corresponding AutomationPeer, adding the required provider behavior, and ensuring the control infrastructure creates your peer.
UI Automation supports both accessibility tools (such as screen readers) and automated testing systems. In both scenarios, external code can inspect UI elements and simulate interaction with your app. For broader platform guidance, see UI Automation Overview.
For teams that prioritize accessibility and automated testing, automation peers are also a reliability contract. Stable automation structure and accurate pattern support improve both assistive technology behavior and long-term test stability.
There are two primary audiences for UI Automation.
- UI Automation clients call UI Automation APIs to discover and interact with currently available UI. For example, a screen reader is a UI Automation client. Clients navigate a tree of automation elements, and can target one app or the full desktop tree.
- UI Automation providers publish information into that tree by implementing APIs for controls introduced by the app. When you build a custom control, you are participating in the provider model and should ensure your control is usable by both accessibility and test clients.
The framework generally exposes parallel API surfaces: one for clients and one for providers. This topic focuses on provider extensibility, especially peer classes and provider interfaces. Client-side APIs are referenced only for context. For client guidance, see UI Automation Client Programmer's Guide.
Note
UI Automation clients are usually desktop apps and don't typically use managed code. UI Automation is based on a standard and not a specific implementation or framework. Many existing UI Automation clients, including assistive technology products such as screen readers, use Component Object Model (COM) interfaces to interact with UI Automation, the system, and the apps that run in child windows. For more info on the COM interfaces and how to write a UI Automation client using COM, see UI Automation Fundamentals.
Determining the existing state of UI Automation support for your custom UI class
Before implementing a custom automation peer, validate whether the base control and its peer already provide the support you need. In many cases, FrameworkElementAutomationPeer, control-specific peers, and their built-in patterns are sufficient. The need for customization depends on how much your control diverges from the base object model and whether template or interaction changes introduce new user experience that require additional accessibility support.
As part of this evaluation, verify that the default peer behavior is sufficient for your automated accessibility tests, not just manual inspection. If your test scenarios depend on specific patterns, names, or hierarchy, implement a custom peer so those expectations remain explicit and maintainable.
Even if base behavior is functionally acceptable, defining your own peer is still recommended when precise ClassName reporting matters for automation and test reliability, especially for controls intended for reuse by others.
Automation peer classes
WinUI and related XAML frameworks build on established UI Automation conventions from earlier managed UI stacks such as Windows Forms, WPF, and Silverlight. Many control and peer concepts follow these established patterns.
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.
Note
For purposes of this topic, we treat the properties that are related to accessibility as being more important when you implement a control peer. But for a more general concept of UI Automation support, you should implement a peer in accordance with recommendations as documented by the UI Automation Provider Programmer's Guide and UI Automation Fundamentals. Those topics don't cover the specific AutomationPeer APIs that you use in WinUI and Windows App SDK, but they do describe the properties that identify your class or provide other information or interaction.
Peers, patterns and control types
A control pattern is an interface implementation that exposes a particular aspect of a control's functionality to a UI Automation client. UI Automation clients use the properties and methods exposed through a control pattern to retrieve information about capabilities of the control, or to manipulate the control's behavior at run time.
Control patterns expose behavior independently of a control's visuals or specific class identity. For example, a tabular control can expose Grid so clients can query dimensions and retrieve items. Clients can use Invoke for invokable controls (such as buttons) and Scroll for scrollable surfaces (such as list controls). Patterns are composable, so a single control can expose multiple behaviors.
Control patterns relate to UI as interfaces relate to COM objects. In COM, you can query an object to ask what interfaces it supports and then use those interfaces to access functionality. In UI Automation, UI Automation clients can query a UI Automation element to find out which control patterns it supports, and then interact with the element and its peered control through the properties, methods, events, and structures exposed by the supported control patterns.
One of the automation peer's primary responsibilities is to declare which patterns are supported. Providers do this by overriding GetPatternCore, which backs GetPattern. Clients query one pattern at a time; if supported, the peer returns an object (typically itself), otherwise it returns null.
A control type broadly classifies the functionality represented by the peer. This differs from a control pattern: patterns describe specific capabilities, while control type describes the higher-level role. Each control type has guidance for these areas:
- UI Automation control patterns: A control type might support more than one pattern, each of which represents a different classification of info or interaction. Each control type has a set of control patterns that the control must support, a set that is optional, and a set that the control must not support.
- UI Automation property values: Each control type has a set of properties that the control must support. These are the general properties, as described in UI Automation Properties Overview, not the ones that are pattern-specific.
- UI Automation events: Each control type has a set of events that the control must support. Again these are general, not pattern-specific, as described in UI Automation Events Overview.
- UI Automation tree structure: Each control type defines how the control must appear in the UI Automation tree structure.
Regardless of framework details, client behavior is not tied to a specific XAML app model. Many existing clients, including assistive technologies, interact through COM. In that model, clients QueryInterface for pattern interfaces or general automation interfaces, and the automation infrastructure marshals calls to your app's provider and peer implementation.
When you implement control patterns for a managed-code Windows app, you can use .NET interfaces to represent these patterns instead of COM interface syntax. For example, the UI Automation pattern interface for a .NET provider implementation of the Invoke pattern is IInvokeProvider.
For a list of control patterns, provider interfaces, and their purpose, see Control patterns and interfaces. For control types, see UI Automation Control Types Overview.
Guidance for how to implement control patterns
Control-pattern guidance belongs to the overall UI Automation platform, not only to a specific app model. When implementing patterns, align behavior with Microsoft documentation and UI Automation conventions. Start with Implementing UI Automation Control Patterns, especially the implementation and required-members sections for each pattern. Although those references often describe native COM interfaces, equivalent provider interfaces are available for WinUI in Microsoft.UI.Xaml.Automation.Provider.
If you're using the default automation peers and expanding on their behavior, those peers have already been written in conformance to UI Automation guidelines. If they support control patterns, you can rely on that pattern support conforming with guidance at Implementing UI Automation Control Patterns. If a control peer reports that it's representative of a control type defined by UI Automation, then the guidance documented at Supporting UI Automation Control Types has been followed by that peer.
Additional interpretation is often required when implementing patterns or control types not covered by default controls. For example, default XAML controls do not implement annotation patterns. If your app depends on annotation workflows, your peer can implement IAnnotationProvider and report a Document control type with properties that communicate annotation capability.
We recommend that you use the guidance that you see for the patterns under Implementing UI Automation Control Patterns or control types under Supporting UI Automation Control Types as orientation and general guidance. The API links provide descriptions and remarks on the purpose of the APIs. For WinUI syntax specifics, use the equivalent API within the Microsoft.UI.Xaml.Automation.Provider namespace.
Built-in automation peer classes
In general, elements expose automation peers when they are interactive or provide meaningful information to assistive technologies. Not all visual elements need peers. For example, Button and TextBox have peers, while Border and Panel-based layout types such as Grid and Canvas do not. A Panel contributes layout only and has no direct accessibility interaction model, so its meaningful child elements are surfaced through the nearest ancestor that has a peer.
UI Automation process boundaries
UI Automation clients that inspect Windows apps usually run out-of-process. UI Automation infrastructure handles cross-process communication. For details, see UI Automation Fundamentals.
OnCreateAutomationPeer
All classes that derive from UIElement contain the protected virtual method OnCreateAutomationPeer. The object initialization sequence for automation peers calls OnCreateAutomationPeer to get the automation peer object for each control and thus to construct a UI Automation tree for run-time use. UI Automation code can use the peer to get information about a control's characteristics and features and to simulate interactive use by means of its control patterns. 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, the object returned by OnCreateAutomationPeer should derive from ButtonBaseAutomationPeer.
If you are writing a custom control and supplying a custom peer, override OnCreateAutomationPeer so it returns a new instance of your peer type. The peer must derive directly or indirectly from AutomationPeer.
For example, the following code declares that custom control NumericUpDown uses NumericUpDownPeer for UI Automation.
using Microsoft.UI.Xaml.Automation.Peers;
...
public class NumericUpDown : RangeBase {
public NumericUpDown() {
// other initialization; DefaultStyleKey etc.
}
...
protected override AutomationPeer OnCreateAutomationPeer()
{
return new NumericUpDownAutomationPeer(this);
}
}
Public Class NumericUpDown
Inherits RangeBase
' other initialization; DefaultStyleKey etc.
Public Sub New()
End Sub
Protected Overrides Function OnCreateAutomationPeer() As AutomationPeer
Return New NumericUpDownAutomationPeer(Me)
End Function
End Class
// NumericUpDown.idl
namespace MyNamespace
{
runtimeclass NumericUpDown : Microsoft.UI.Xaml.Controls.Primitives.RangeBase
{
NumericUpDown();
Int32 MyProperty;
}
}
// NumericUpDown.h
...
struct NumericUpDown : NumericUpDownT<NumericUpDown>
{
...
Microsoft::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer()
{
return winrt::make<MyNamespace::implementation::NumericUpDownAutomationPeer>(*this);
}
};
//.h
public ref class NumericUpDown sealed : Microsoft::UI::Xaml::Controls::Primitives::RangeBase
{
// other initialization not shown
protected:
virtual AutomationPeer^ OnCreateAutomationPeer() override
{
return ref new NumericUpDownAutomationPeer(this);
}
};
Note
The OnCreateAutomationPeer implementation should do nothing more than initialize a new instance of your custom automation peer, passing the calling control as owner, and return that instance. Do not attempt additional logic in this method. In particular, any logic that could potentially lead to destruction of the AutomationPeer within the same call may result in unexpected runtime behavior.
In typical OnCreateAutomationPeer implementations, the owner is this or Me, because the override executes within the control instance scope.
You can define peer classes in the same file as the control or in separate files. Framework peer types live in Microsoft.UI.Xaml.Automation.Peers, but your custom peers can live in any namespace, provided required namespaces are referenced where OnCreateAutomationPeer is implemented.
Choosing the correct peer base class
Derive your custom AutomationPeer from the closest peer base class that matches your control's functional base class. In the previous example, NumericUpDown derives from RangeBase, so RangeBaseAutomationPeer is the correct peer base. This lets you reuse existing IRangeValueProvider behavior rather than re-implementing it.
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, derive the custom peer class from FrameworkElementAutomationPeer.
If you derive from ContentControl directly, that class has no default automation peer behavior because there is no OnCreateAutomationPeer implementation that references a peer class. So make sure either to implement OnCreateAutomationPeer to use your own peer, or to use FrameworkElementAutomationPeer as the peer if that level of accessibility support is adequate for your control.
Note
You don't typically derive from AutomationPeer rather than FrameworkElementAutomationPeer. If you did derive directly from AutomationPeer you'll need to duplicate a lot of basic accessibility support that would otherwise come from FrameworkElementAutomationPeer.
Initialization of a custom peer class
A custom peer should expose a type-safe constructor that takes the owner control and passes it to the base class initializer. In the next example, owner is passed to RangeBaseAutomationPeer, and eventually FrameworkElementAutomationPeer uses that value to set FrameworkElementAutomationPeer.Owner.
public NumericUpDownAutomationPeer(NumericUpDown owner): base(owner)
{}
Public Sub New(owner As NumericUpDown)
MyBase.New(owner)
End Sub
// NumericUpDownAutomationPeer.idl
import "NumericUpDown.idl";
namespace MyNamespace
{
runtimeclass NumericUpDownAutomationPeer : Microsoft.UI.Xaml.Automation.Peers.AutomationPeer
{
NumericUpDownAutomationPeer(NumericUpDown owner);
Int32 MyProperty;
}
}
// NumericUpDownAutomationPeer.h
...
struct NumericUpDownAutomationPeer : NumericUpDownAutomationPeerT<NumericUpDownAutomationPeer>
{
...
NumericUpDownAutomationPeer(MyNamespace::NumericUpDown const& owner);
};
//.h
public ref class NumericUpDownAutomationPeer sealed : Microsoft::UI::Xaml::Automation::Peers::RangeBaseAutomationPeer
//.cpp
public: NumericUpDownAutomationPeer(NumericUpDown^ owner);
Core methods of AutomationPeer
Overridable automation methods are exposed as pairs: a public method used by provider infrastructure and a protected Core method intended for customization. By default, the public method forwards to the corresponding Core method, falling back to base implementation when needed.
When implementing a peer for a custom control, override Core methods wherever your behavior differs from base peer behavior. UI Automation retrieves information through public methods, but your customization point is the corresponding Core override.
At a minimum, whenever you define a new peer class, implement the GetClassNameCore method, as shown in the next example.
protected override string GetClassNameCore()
{
return "NumericUpDown";
}
Note
You might want to store the strings as constants rather than directly in the method body, but that is up to you. For GetClassNameCore, you won't need to localize this string. The LocalizedControlType property is used any time a localized string is needed by a UI Automation client, not ClassName.
GetAutomationControlType
Some assistive technologies report GetAutomationControlType alongside the UI Automation Name. If your control's role differs meaningfully from the base class role, implement a custom peer and override GetAutomationControlTypeCore. This is especially important when deriving from generalized bases such as ItemsControl or ContentControl.
Your implementation of GetAutomationControlTypeCore describes your control by returning an AutomationControlType value. Although you can return AutomationControlType.Custom, you should return one of the more specific control types if it accurately describes your control's main scenarios. Here's an example.
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Spinner;
}
Note
Unless you specify AutomationControlType.Custom, you don't have to implement GetLocalizedControlTypeCore to provide a LocalizedControlType property value to clients. UI Automation common infrastructure provides translated strings for every possible AutomationControlType value other than AutomationControlType.Custom.
GetPattern and GetPatternCore
GetPatternCore returns the object that supports the requested pattern. A UI Automation client requests a specific PatternInterface through GetPattern. If supported, the override should return the implementing object, typically the peer itself. If unsupported, return null (often by delegating to base behavior and letting it return null).
When a pattern is supported, GetPatternCore can return this or Me. Clients then cast the GetPattern return value to the requested interface.
If a peer class inherits from another peer, and all necessary support and pattern reporting is already handled by the base class, implementing GetPatternCore isn't necessary. For example, if you are implementing a range control that derives from RangeBase, and your peer derives from RangeBaseAutomationPeer, that peer returns itself for PatternInterface.RangeValue and has working implementations of the IRangeValueProvider interface that supports the pattern.
Although it is not the literal code, this example approximates the implementation of GetPatternCore already present in RangeBaseAutomationPeer.
protected override object GetPatternCore(PatternInterface patternInterface)
{
if (patternInterface == PatternInterface.RangeValue)
{
return this;
}
return base.GetPatternCore(patternInterface);
}
If you are implementing a peer where you don't have all the support you need from a base peer class, or you want to change or add to the set of base-inherited patterns that your peer can support, then you should override GetPatternCore to enable UI Automation clients to use the patterns.
For a list of UI Automation provider patterns supported by WinUI, see Microsoft.UI.Xaml.Automation.Provider. Each such pattern has a corresponding value of the PatternInterface enumeration, which is how UI Automation clients request the pattern in a GetPattern call.
A peer can report that it supports more than one pattern. If so, the override should include return path logic for each supported PatternInterface value and return the peer in each matching case. It is expected that the caller will request only one interface at a time, and it is up to the caller to cast to the expected interface.
Here's an example of a GetPatternCore override for a custom peer. It reports the support for two patterns, IRangeValueProvider and IToggleProvider. The control here is a media display control that can display as full-screen (the toggle mode) and that has a progress bar within which users can select a position (the range control). This code came from the XAML accessibility sample (archived legacy sample).
protected override object GetPatternCore(PatternInterface patternInterface)
{
if (patternInterface == PatternInterface.RangeValue)
{
return this;
}
else if (patternInterface == PatternInterface.Toggle)
{
return this;
}
return null;
}
Forwarding patterns from sub-elements
A GetPatternCore method implementation can also specify a sub-element or part as a pattern provider for its host. This example mimics how ItemsControl transfers scroll-pattern handling to the peer of its internal ScrollViewer control. To specify a sub-element for pattern handling, this code gets the sub-element object, creates a peer for the sub-element by using the FrameworkElementAutomationPeer.CreatePeerForElement method, and returns the new peer.
protected override object GetPatternCore(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.GetPatternCore(patternInterface);
}
Other Core methods
Your control may need keyboard alternatives for core scenarios; for more details, see Keyboard accessibility. Key behavior belongs in control logic, but the peer should report key metadata through GetAcceleratorKeyCore and GetAccessKeyCore. If key descriptions are user-facing, localize them through resources.
If your control exposes collections, use functional and peer base classes that already implement collection behavior. Otherwise, override GetChildrenCore to accurately represent parent-child relationships in the automation tree.
Implement IsContentElementCore and IsControlElementCore to indicate whether your control should be treated as content, control, or both. Both default to true. These values help clients such as screen readers filter and navigate the tree effectively. If GetPatternCore forwards behavior to a sub-element peer, the sub-element can return false from IsControlElementCore to stay out of the exposed tree.
Some controls may support labeling scenarios, where a text label part supplies information for a non-text part, or a control is intended to be in a known labeling relationship with another control in the UI. If it's possible to provide a useful class-based behavior, you can override GetLabeledByCore to provide this behavior.
GetBoundingRectangleCore and GetClickablePointCore are used mainly for automated testing scenarios. If you want to support automated testing for your control, you might want to override these methods. This might be desired for range-type controls, where you can't suggest just a single point because where the user clicks in coordinate space has a different effect on a range. For example, the default ScrollBar automation peer overrides GetClickablePointCore to return a "not a number" Point value.
GetLiveSettingCore influences the control default for the LiveSetting value for UI Automation. You might want to override this if you want your control to return a value other than AutomationLiveSetting.Off. For more info on what LiveSetting represents, see AutomationProperties.LiveSetting.
You might override GetOrientationCore if your control has a settable orientation property that can map to AutomationOrientation. The ScrollBarAutomationPeer and SliderAutomationPeer classes do this.
Base implementation in FrameworkElementAutomationPeer
The FrameworkElementAutomationPeer base implementation provides automation information inferred from framework-level layout and behavior state.
- GetBoundingRectangleCore: Returns a Rect structure based on the known layout characteristics. Returns a 0-value Rect if IsOffscreen is true.
- GetClickablePointCore: Returns a Point structure based on the known layout characteristics, as long as there is a nonzero BoundingRectangle.
- GetNameCore: More extensive behavior than can be summarized here; see GetNameCore. Basically, it attempts a string conversion on any known content of a ContentControl or related classes that have content. Also, if there is a value for LabeledBy, that item's Name value is used as the Name.
- HasKeyboardFocusCore: Evaluated based on the owner's FocusState and IsEnabled properties. Elements that aren't controls always return false.
- IsEnabledCore: Evaluated based on the owner's IsEnabled property if it is a Control. Elements that aren't controls always return true. This doesn't mean that the owner is enabled in the conventional interaction sense; it means that the peer is enabled despite the owner not having an IsEnabled property.
- IsKeyboardFocusableCore: Returns true if owner is a Control; otherwise it is false.
- IsOffscreenCore: A Visibility of Collapsed on the owner element or any of its parents equates to a true value for IsOffscreen. Exception: a Popup object can be visible even if its owner's parents are not.
- SetFocusCore: Calls Focus.
- GetParent: Calls FrameworkElement.Parent from the owner, and looks up the appropriate peer. This isn't an override pair with a "Core" method, so you can't change this behavior.
Note
Default framework peers implement behavior by using internal native code, not necessarily by using projected managed code. You won't be able to inspect full implementation details through common language runtime (CLR) reflection or similar techniques. You also won't see distinct reference pages for subclass-specific overrides of base peer behavior. For example, there might be additional behavior for GetNameCore of a TextBoxAutomationPeer, which won't be described on the AutomationPeer.GetNameCore reference page, and there is no dedicated TextBoxAutomationPeer.GetNameCore reference page. Instead, read the reference topic for the most immediate peer class and look for implementation notes in the Remarks section.
Peers and AutomationProperties
Your automation peer should provide sensible default values for accessibility metadata. You can still override parts of that metadata with AutomationProperties attached properties on control instances. This applies to both built-in and custom controls. For example: <Button AutomationProperties.Name="Special" AutomationProperties.HelpText="This is a special button."/>
For more info about AutomationProperties attached properties, see Basic accessibility information.
Some AutomationPeer methods exist to satisfy general provider contracts but are rarely customized in control peers, because the app typically supplies that data through AutomationProperties. For example, many apps establish labeling relationships with AutomationProperties.LabeledBy. In contrast, LabeledByCore is usually implemented only in peers that model internal item/header relationships.
Implementing patterns
Consider a control that exposes expand/collapse behavior. Its peer should return itself when GetPattern is queried for PatternInterface.ExpandCollapse, implement IExpandCollapseProvider, and define Expand, Collapse, and ExpandCollapseState.
Design accessibility into the control API itself. When behavior can be triggered either by direct UI interaction or by an automation pattern, route both paths through shared control logic. For example, if button handlers and keyboard commands can expand/collapse state, they should call the same internal methods used by peer implementations of Expand and Collapse. This keeps behavior and visual state transitions consistent, regardless of invocation path.
A typical implementation is that the provider APIs first call Owner for access to the control instance at run time. Then the necessary behavior methods can be called on that object.
public class IndexCardAutomationPeer : FrameworkElementAutomationPeer, IExpandCollapseProvider {
private IndexCard ownerIndexCard;
public IndexCardAutomationPeer(IndexCard owner) : base(owner)
{
ownerIndexCard = owner;
}
}
An alternate implementation is that the control itself can reference its peer. This is a common pattern if you are raising automation events from the control, because the RaiseAutomationEvent method is a peer method.
UI Automation events
UI Automation events generally fall into these categories.
| Event | Description |
|---|---|
| Property change | Fires when a property on a UI Automation element or control pattern changes. For example, if a client needs to monitor an app's check box control, it can register to listen for a property change event on the ToggleState property. When the check box control is checked or unchecked, the provider fires the event and the client can act as necessary. |
| Element action | Fires when a change in the UI results from user or programmatic activity; for example, when a button is clicked or invoked through the Invoke pattern. |
| Structure change | Fires when the structure of the UI Automation tree changes. The structure changes when new UI items become visible, hidden, or removed on the desktop. |
| Global change | Fires when actions of global interest to the client occur, such as when the focus shifts from one element to another, or when a child window closes. Some events do not necessarily mean that the state of the UI has changed. For example, if the user tabs to a text-entry field and then clicks a button to update the field, a TextChanged event fires even if the user did not actually change the text. When processing an event, it may be necessary for a client application to check whether anything has actually changed before taking action. |
AutomationEvents identifiers
UI Automation events are identified by AutomationEvents values. The values of the enumeration uniquely identify the kind of event.
Raising events
UI Automation clients can subscribe to automation events. In the automation peer model, peers for custom controls must report changes to control state that are relevant to accessibility by calling the RaiseAutomationEvent method. Similarly, when a key UI Automation property value changes, custom control peers should call the RaisePropertyChangedEvent method.
The next code example shows how to get the peer object from within the control definition code and call a method to fire an event from that peer. As an optimization, the code determines whether there are any listeners for this event type. Firing the event and creating the peer object only when there are listeners avoids unnecessary overhead and helps the control remain responsive.
if (AutomationPeer.ListenerExists(AutomationEvents.PropertyChanged))
{
NumericUpDownAutomationPeer peer =
FrameworkElementAutomationPeer.FromElement(nudCtrl) as NumericUpDownAutomationPeer;
if (peer != null)
{
peer.RaisePropertyChangedEvent(
RangeValuePatternIdentifiers.ValueProperty,
(double)oldValue,
(double)newValue);
}
}
Peer navigation
After obtaining a peer, UI Automation clients can navigate an app's peer structure by calling GetChildren and GetParent. Within a control, child navigation is produced by GetChildrenCore, which UI Automation uses to build the subtree (for example, items in a list). The default implementation in FrameworkElementAutomationPeer traverses the visual tree. Custom controls can override this to expose a more meaningful automation representation.
Native automation support for text patterns
Some default framework peers support text patterns (PatternInterface.Text) through native implementation details, so ITextProvider may not appear in managed inheritance. Even so, pattern queries from managed or unmanaged clients can still report text-pattern support and expose corresponding behavior.
If you intend to derive from one of the text controls and also create a custom peer that derives from one of the text-related peers, check the Remarks sections in the API reference for the peer to learn more about any native-level support for patterns. You can access the native base behavior in your custom peer if you call the base implementation from your managed provider interface implementations, but it's difficult to modify what the base implementation does because the native interfaces on both the peer and its owner control aren't exposed. Generally you should either use the base implementations as-is (call base only) or completely replace the functionality with your own managed code and don't call the base implementation. The latter is an advanced scenario, and it requires strong familiarity with the text services framework used by your control to satisfy accessibility requirements.
AutomationProperties.AccessibilityView
In addition to custom peers, you can adjust automation-tree representation per control instance by setting AutomationProperties.AccessibilityView in XAML. This is not peer code, but it is often important when refining accessibility for custom templates.
The primary use case is omitting template parts that do not contribute meaningful accessibility semantics for the composed control. To do this, set AutomationProperties.AccessibilityView to "Raw".
Throwing exceptions from automation peers
Peer implementations are allowed to throw exceptions, and robust clients are expected to continue processing. In practice, clients often inspect automation trees spanning multiple apps, so failure in one subtree should not terminate the client process.
Input validation is appropriate. For example, throw ArgumentNullException when null is invalid for your implementation. Also account for timing: peer interactions are not always synchronized with current visual state, so control state may change between peer creation and method execution. For those scenarios, providers can use two dedicated exceptions:
- Throw ElementNotAvailableException if you're unable to access either the peer's owner or a related peer element based on the original info your API was passed. For example, you might have a peer that's trying to run its methods but the owner has since been removed from the UI, such as a modal dialog that's been closed. For a non-.NET client, this maps to UIA_E_ELEMENTNOTAVAILABLE.
- Throw ElementNotEnabledException if there still is an owner, but that owner is in a mode such as IsEnabled
=false that's blocking some of the specific programmatic changes that your peer is trying to accomplish. For a non-.NET client, this maps to UIA_E_ELEMENTNOTENABLED.
More generally, be conservative with exceptions. Many clients cannot convert provider exceptions into meaningful user actions. In some paths, no-op behavior or local exception handling is preferable to repeatedly throwing. Also remember that many UI Automation clients are COM-based and primarily evaluate HRESULT results such as S_OK.
Related topics
Windows developer