Accessibility: Provider From Scratch

Table of contents

Introduction

Notice this page does not explain how to implement a custom provider for Windows Forms or Moonlight Controls, instead explains how to implement a provider for the mono-a11y implementation.

If you want to support accessibility using UIA in your custom controls then you should follow Microsoft documentation:

This page is complementary to Windows Forms Implementation.

Quick Steps List

Basically to implement a new mono-a11y provider you should use the following list of steps:

  1. Select your not-implemented provider. Read Winform Control Status.
  2. Write a simple Winform Application that includes your selected Winform Control and run it in Windows Vista to examine it using UISpy. (Note: UISpy isn't included in MS VS 2008, so you need to download "Windows Vista SDK" from MSDN)
  3. In UISpy look for the Automation Property labeled as "Control Type", then read the documentation associated.
  4. In your Control Type documentation read the table under the section "Required UI Automation Tree Structure", don't forget to verify UISpy information to match the implementation. If your provider supports navigation you must subclass FragmentRootControlProvider, otherwise you must subclass FragmentControlProvider. This is important to generate a valid UI Automation Tree. The difference between FragmentRootControlProvider and FragmentControlProvider is that FragmentRootControlProvider should add/remove children and generate events to keep navigation updated.
  5. Both specializations (either subclassing from FragmentRootControlProvider or FragmentControlProvider) must support the following:
    1. Automation Properties, defined in the table "Required UI Automation Properties", and
    2. Provider Interfaces, defined in the table "Required UI Automation Control Patterns",
  6. In your Provider Interface Specialization you have to generate your Automation Events and Automation Property Event.

Generating UI Automation Tree

Two methods defined in FragmentRootControlProvider are used to initialize and finalize children: InitializeChildControlStructure and FinalizeChildControlStructure. By default FragmentRootControlProvider uses Control.ControlAdded and Control.ControlRemoved events to do so, if your provider's Control does not use those events then, you need to override the methods to generate the valid tree by using the following protected methods:

  • OnNavigationChildAdded: Adds child to. Use true as first argument if you want to generate your Automation Structure Changed Child Added Event.
  • OnNavigationChildRemoved: Removes child. Use true as first argument if you want to generate your Automation Structure Changed Child Removed Event.
  • OnNavigationChildrenCleared: Clears children. Use true as argument if you want to generate your Automation Structure Changed Children Removed Event.

Also, you can use the following public methods:

  • AddChildProvider
  • RemoveChildProvider

both methods are wrappers to OnNavigationChildAdded and OnNavigationChildRemoved.

If you override InitializeChildControlStructure don't forget to add your already added children.

Implementing Automation Events

There are 3 basic types of Automation Events, read UI Automation Events Overview for detailed explanation:

  • Structure Changed Events. Generated when calling FragmentRootControlProvider methods: OnNavigationChildAdded, OnNavigationChildRemoved, OnNavigationChildrenCleared, AddChildProvider and RemoveChildProvider.
  • Automation Events: Generated by your Control depending on internal states.
  • Automation Property Events: Generated by your Control depending on internal states.

There are Automation Events and Automation Property Events always generated (does not matter the specialized provider), those are defined in the AutomationElementIdentifiers, most of the events are already defined in the base class of FragmentRootControlProvider and FragmentControlProvider: SimpleControlProvider, however depending on your Control and Pattern supported you may need to enable or disable it. For example to enable IsEnabledProperty you should do the following:

SetEvent (ProviderEventType.AutomationElementIsEnabledProperty, new AutomationIsEnabledPropertyEvent (this));

and to disable it

SetEvent (ProviderEventType.AutomationElementIsEnabledProperty, null);

To set AutomationElementIdentifiers you should override InitializeEvents, however don't forget to call base implementation.

However, you must implement your Automation Events and Automation Property Events for each provider specialized, depending on the pattern. For example, if your provider support Range Value Pattern by specializing IRangeValueProvider, then you should generate the events for each of their properties (see Range Value Provider specialization in ScrollBar for a working example).

To implement your Automation Events you have to either subclass from BaseAutomationEvent if your event is Automation Event and subclass from BaseAutomationPropertyEvent if your event is Automation Property Event. In both classes you have to override Connect and Disconnect methods to associate/disassociate your Control events that should generate the Automation Event. When your Control Event is generated you have to either call BaseAutomationPropertyEvent.RaiseAutomationPropertyChangedEvent if you are subclassing from BaseAutomationPropertyEvent or BaseAutomationEvent.RaiseAutomationEvent if you are subclass from BaseAutomationEvent.

For example RangeValuePattern.ValueProperty in ScrollBar implementation subclasses BaseAutomationPropertyEvent, and InvokePattern.InvokedEvent in ScrollBar subclasses BaseAutomationEvent.