How it works

Everything is based on a class named AutomationPeer. When an UI Automation client analyzes your User Interface, it looks for an automation peer and uses it to walks trough the tree of peers. The tree of peers is nearly a visual tree but it exposes only the relevant part of the interface.

 Every standard controls have a regarding automation peer already defined. But if you add new parts on them or if you built from scratch a new control, you have to provide a related automation peer.

 If not the automation peer will not be aware of this parts and they won’t be available to the automation clients. Also if a standard control is used but the “normal” visual tree is broken, for example by splitting the normal items in several parts, it is necessary to expose the news parts. I’ve seen this case in some controls where the content of the standard content presenter where taken apart to be place in another place of the Visual Tree. As you will see, this is done in the GetChildrenCore method of the new AutomationPeer.

 

 How to make your controls available to the UI Automation client

 The AutomationPeer are created and retrieved for each control in the method named OnCreateAutomationPeer. The custom control must overrides this method to provide an AutomationPeer related to its specific implementation.

 

If the control is build from scratch and its base class is Control, there is no built-in automation peer because there is simply none at all for the Control class. However, the FrameworkElementAutomationPeer class can be used as a base class.

If the controls inherits from an another standard one like TabControl, it’s possible to inherit from its regarding AutomationPeer and customize its behavior. This is especially needed if new controls are added to the template of the control because the basic AutomationPeer(let’s say TabControlAutomationPeer) does not know about them. By convention, the automation peer classes name starts with the name of the related control and end with AutomationPeer.

 

There is several methods in the AutomationPeer that can be overridden to make it works the right way. The embolden methods are the one which are required at least:

  • GetClassNameCore: it returns the name of the class the automationpeer is builded for.
  • GetAutomationControlTypeCore : returns an enum telling which behavior implements the automated control. For example it can be Button, Tab, Window, etc.
  • GetPattern: tells which kind of functional behavior is fulfilled by the control: IRangeValueprovider, ITextProvider, IValueProvider, etc.
  • GetChildrenCore: this methods returns a list of automationPeer which are related to the children of your control. If you add a new visual part to a standard control, this is where you must add the new child.
  • IsControlElementCore and IsContentElementCore : tells the UI Automation client which type of automated control it is : for reading or for interactive purpose. These can improve the performance when being used as a filter.

 

Also, you can use the static method  CreatePeerForElement of the UIElementAutomationPeer class to create an AutomationPeer of a know control. This is especially helpful when new control have to be added.

 Finally, the UI Automation clients do not follows the changes in the UI and they have to be noticed manually. This is done via the RaisePropertyChanged on the relative automation peer and have to be done on every change. This is very familiar from what it is necessary to do in the INotifyPropertyChanged to notify the binding of a change.

 Here is an example of an AutomationPeer for the HeaderedControl in the Amazing wpf control library.

   1: public class HeaderControlAutomationPeer : FrameworkElementAutomationPeer
   2: {
   3:     public HeaderControlAutomationPeer(FrameworkElement owner)
   4:         : base(owner) 
   5:        { 
   6:        if (!(owner is HeaderedControl)) 
   7:          throw new ArgumentOutOfRangeException(); 
   8:        }
   9:  
  10:     protected override string GetNameCore()
  11:     {
  12:         return "HeaderContentControl";
  13:     }
  14:  
  15:     protected override AutomationControlType GetAutomationControlTypeCore()
  16:     {
  17:         return AutomationControlType.Header;
  18:     }
  19:  
  20:     protected override System.Collections.Generic.List<AutomationPeer> GetChildrenCore()
  21:     {
  22:         List<AutomationPeer> peers = base.GetChildrenCore();
  23:         var peer = UIElementAutomationPeer
  24:                    .CreatePeerForElement(MyOwner.PART_Header);
  25:         if (peer != null) peers.Add(peer);
  26:         return peers;
  27:     }
  28:  
  29:     private HeaderedControl MyOwner
  30:     {
  31:         get { return (HeaderedControl)base.Owner; }
  32:     }
  33: }

  

And here is an example of code you can implement to raise the property changed event on a peer :

  1: if (AutomationPeer.ListenerExists(AutomationEvents.PropertyChanged))

   2: {
   3:     HeaderControlAutomationPeer peer =
   4:         UIElementAutomationPeer.FromElement(PART_Header) 
   5:                 as HeaderControlAutomationPeer;
   6:  
   7:     if (peer != null)
   8:     {
   9:         peer.RaisePropertyChangedEvent(
  10:             RangeValuePatternIdentifiers.ValueProperty,
  11:             (double)oldValue,
  12:             (double)newValue);
  13:     }
  14: }

  

Interesting links

Comments are wide open if you have any question !

Shout it kick it on DotNetKicks.com