Implementing a Server-Side UI Automation Provider
This topic describes how to implement a server-side Microsoft UI Automation provider for a custom control written in C++. It contains the following sections:
- Provider Interfaces
- Required Functionality for UI Automation Providers
- Property Values
- Events from Providers
- Provider Navigation
- Assigning a New Parent
- Provider Repositioning
- Disconnecting Providers
- Related topics
For code examples that show how to implement server-side providers, see How-To Topics for UI Automation Providers.
The following Component Object Model (COM) interfaces provide functionality for custom controls. To provide basic functionality, every UI Automation provider must implement at least the IRawElementProviderSimple interface. The IRawElementProviderFragment and IRawElementProviderFragmentRoot interfaces are optional, but should be implemented for elements in a complex control to provide additional functionality.
|IRawElementProviderSimple||Provides basic functionality for a control hosted in a window, including support for control patterns and properties.|
|IRawElementProviderFragment||Adds functionality for an element in a complex control, including navigating in the fragment, setting focus, and returning the bounding rectangle of the element.|
|IRawElementProviderFragmentRoot||Adds functionality for the root element in a complex control, including locating a child element at specified coordinates and setting the focus state for the entire control.|
Note In the UI Automation API for managed code, these interfaces form an inheritance hierarchy. This is not the case in C++, where the interfaces are completely separate.
The following interfaces provide added functionality but implementation is optional.
|IRawElementProviderAdviseEvents||Enables the provider to track requests for events.|
|IRawElementProviderHwndOverride||Enables repositioning of window-based elements in the UI Automation tree of a fragment.|
To communicate with UI Automation, your control must implement the main areas of functionality described in the following table.
|Expose the provider to UI Automation.||In response to a WM_GETOBJECT message sent to the control window, return the object that implements IRawElementProviderSimple. For fragments, this must be the provider for the fragment root.|
|Provide property values.||Implement IRawElementProviderSimple::GetPropertyValue to provide or override values.|
|Enable the client to interact with the control.||Implement interfaces that support each appropriate control pattern, such as IInvokeProvider. Return these control pattern providers in your implementation of IRawElementProviderSimple::GetPatternProvider.|
|Raise events.||UiaRaiseAutomationEvent, methods of IProxyProviderWinEventSink.|
|Enable navigating and focusing in a fragment.||Implement IRawElementProviderFragment for each element within the fragment. Not necessary for elements that are not part of a fragment.|
|Enable focusing and locating child elements in a fragment.||Implement IRawElementProviderFragmentRoot. Not necessary for elements that are not fragment roots.|
UI Automation providers for custom controls must support certain properties that can be used by UI Automation and by client applications. For elements that are hosted in windows, UI Automation can retrieve some properties from the default window provider, but must obtain others from the custom provider.
Typically, providers for window-based controls do not need to provide the following properties that are identified by PROPERTYID:
The RuntimeId property of a simple element or fragment root hosted in a window is obtained from the window. However, fragment elements below the root, such as list items in a list box, must provide their own identifiers. For more information, see IRawElementProviderFragment::GetRuntimeId.
The IsKeyboardFocusable property should be returned for providers hosted in a Windows Forms control. In this case, the default window provider may be unable to retrieve the correct value.
The Name property is usually supplied by the host provider.
UI Automation providers should raise events to notify client applications of changes in the state of the UI. The following functions are used to raise events.
|UiaRaiseAutomationEvent||Raises various events, including events triggered by control patterns.|
|UiaRaiseAutomationPropertyChangedEvent||Raises an event when a UI Automation property has changed.|
|UiaRaiseStructureChangedEvent||Raises an event when the structure of the UI Automation tree has changed, for example, by removing or adding an element.|
The purpose of an event is to notify the client of something taking place in the UI. Providers should raise an event regardless of whether the change was triggered by user input or by a client application using UI Automation. For example, the event identified by UIA_Invoke_InvokedEventId should be raised whenever the control is invoked, either through direct user input or by the client application calling IUIAutomationInvokePattern::Invoke.
To optimize performance, a provider can selectively raise events, or raise no events at all if no client application is registered to receive them. The following API elements are used for optimization.
|UiaClientsAreListening||This function ascertains whether any client applications have subscribed to UI Automation events.|
|IRawElementProviderAdviseEvents||Implementing this interface on a fragment root enables the provider to be advised when clients register and unregister event handlers for events on the fragment.|
Note Similar to implementing reference counting in COM programming, it is important for UI Automation providers to treat the IRawElementProviderAdviseEvents::AdviseEventAdded and AdviseEventRemoved methods like the IUnknown::AddRef and Release methods of the IUnknown interface. As long as AdviseEventAdded has been called more times than AdviseEventRemoved for a specific event or property, the provider should continue to raise corresponding events, because some clients are still listening. Alternatively, UI Automation providers can use the UiaClientsAreListening function to determine whether at least one client is listening and, if so, raise all appropriate events.
Providers for simple controls, such as a custom button hosted in a window, do not need to support navigation in the UI Automation tree. Navigation to and from the element is handled by the default provider for the host window, which is specified in the implementation of IRawElementProviderSimple::HostRawElementProvider. When you implement a provider for a complex custom control, however, you must support navigation between the root node of the fragment and its descendants, and between sibling nodes.
Note Elements of a fragment other than the root must return NULL from HostRawElementProvider, because they are not directly hosted in a window, and no default provider can support navigation to and from them.
The structure of the fragment is determined by your implementation of IRawElementProviderFragment::Navigate. For each possible direction from each fragment, this method returns the provider object for the element in that direction. If there is no element in that direction, the method returns NULL.
The fragment root supports navigation only to child elements. For example, a list box returns the first item in the list when the direction is NavigateDirection_FirstChild, and returns the last item when the direction is NavigateDirection_LastChild. The fragment root does not support navigation to a parent or to siblings; this is handled by the host window provider.
Elements of a fragment that are not the root must support navigation to the parent, and to any siblings and children they have.
Pop-up windows are actually top-level windows, and by default, appear in the UI Automation tree as children of the desktop. In many cases, however, pop-up windows are logically children of some other control. For example, the drop-down list of a combo box is logically a child of the combo box. Similarly, a menu pop-up window is logically a child of the menu. UI Automation provides support to assign a new parent to a pop-up window so that it appears to be a child of the associated control.
To assign a new parent to a pop-up window:
- Create a provider for the pop-up window. This requires that the class of the pop-up window be known in advance.
- Implement all properties and control patterns as usual for that pop-up, as though it were a control in its own right.
- Implement the IRawElementProviderSimple::HostRawElementProvider property so that it returns the value obtained from UiaHostProviderFromHwnd, where the parameter is the window handle of the pop-up window.
- Implement IRawElementProviderFragment::Navigate for the pop-up window and its parent so that navigation is handled properly from the logical parent to the logical children, and between sibling children.
When UI Automation encounters the pop-up window, it recognizes that navigation is being overridden from the default, and skips over the pop-up window when it is encountered as a child of the desktop. Instead, the node is reachable only through the fragment.
Assigning a new parent is not suitable for cases where a control can host a window of any class. For example, a rebar control can host any type of window in its bands. To handle these cases, UI Automation supports an alternative form of window relocation, as described in the next section.
UI Automation fragments may contain two or more elements that are each contained in a window. Because each window has its own default provider that considers the window to be a child of a containing window, the UI Automation tree by default, will show the windows in the fragment as children of the parent window. In most cases this is desirable behavior, but sometimes it can lead to confusion because it does not match the logical structure of the UI.
A good example of this is a rebar control. A rebar control contains bands, each of which can in turn contain a window-based control, such as a toolbar, an edit box, or a combo box. The default window provider for the rebar window sees the band control windows as children, and the rebar provider sees the bands as children. Because the window provider and the rebar provider are working in tandem and combining their children, both the bands and the window-based controls appear as children of the rebar control. Logically, however, only the bands should appear as children of the rebar control, and each band provider should be coupled with the default window provider for the control it contains.
To accomplish this, the fragment root provider for the rebar control exposes a set of children representing the bands. Each band has a single provider that may expose properties and control patterns. In its implementation of IRawElementProviderSimple::HostRawElementProvider, the band provider returns the default window provider for the control window, which it obtains by calling UiaHostProviderFromHwnd, passing in the control's window handle (HWND). Finally, the fragment root provider for the rebar implements the IRawElementProviderHwndOverride interface, and in its implementation of IRawElementProviderHwndOverride::GetOverrideProviderForHwnd, it returns the appropriate band provider for the control contained in the specified window.
Applications typically create controls as they are needed and destroy them afterward. After destroying a control, the UI Automation provider resources associated with the control should be released by calling the UiaDisconnectProvider.
Similarly, an application should use the UiaDisconnectAllProviders function to release all UI Automation resources held by all providers in the application before shutting down.