Custom UI Automation Providers in Depth: Part 2

In Part 1, we introduced the idea of UI Automation custom control providers and created a TriColor control as a sample.  Now, we’re going to try to add one basic property: the “Hello World!” of UI Automation.  Here’s the sample code for this part.

To do this, we need to create a new object, called a provider, that answers questions on behalf of the control.  That object will implement the IRawElementProviderSimple interface, which defines its contract with UI Automation.  The interface name, although long, points out the important parts of this contract:

  • It is an ElementProvider, distinguishing it from other kinds of providers in Windows.
  • It is Raw, as opposed to the COM interface that a client would use.  This interface is intended to be easy to implement, rather than easy to consume.  (UI Automation itself provides the client objects and interfaces.)
  • This is the Simple interface.  There are more advanced interfaces with different suffixes.

There are just four methods on the interface, answering four basic questions about the object:

  • get_ProviderOptions(): What kind of provider is it? 
  • get_HostRawElementProvider(): What HWND does this belong to?
  • GetPropertyValue(): What are its properties?
  • GetPatternProvider(): What are its Control Patterns? (covered in a later lesson)

So, we simply declare a new object in our project, TriColorProvider, and start implementing this interface, starting with get_ProviderOptions:

        public const int ProviderOptionUseComThreading = 0x20;
        public override ProviderOptions ProviderOptions
        {
            // Request COM threading style - all calls on main thread
            get
            {
                return (ProviderOptions)((int)ProviderOptions.ServerSideProvider | ProviderOptionUseComThreading);
            }
        }

ServerSideProvider describes where our provider runs: inside the server (UI) process.  Also, I’m using a new Windows 7 feature here – I can tell UIA to use STA COM-style threading, such that all provider calls will be made to the thread that created my provider.  This avoids threading issues when I need to communicate with UI elements on the UI thread. 

The HostRawElementProvider method is similarly easy.  This function returns the HWND provider that corresponds to this control and supplies some basic properties for it, as we noted in the last post.  I just need to call the HostProviderFromHandle() function with my window handle and return its result.  At this point, though, I started thinking: this seems like cookie-cutter code.  If I ever have a second provider, I will have almost the same function body, but a different window handle. 

So, I refactored the class into a BaseSimpleProvider and a TriColorProvider.  The base provider will implement the interface literally, but delegate where useful to protected virtual methods, which the TriColorProvider can override.  This helps to separate what is common from what varies between classes.  If I ever need to write a second provider, I will have much less work to do.  I add the following method to BaseSimpleProvider:

        public IRawElementProviderSimple HostRawElementProvider
        {
            get
            {
                IntPtr hwnd = this.GetWindowHandle();
                if (hwnd != IntPtr.Zero)
                {
                    return AutomationInteropProvider.HostProviderFromHandle(this.GetWindowHandle());
                }
                else
                {
                    return null;
                }
            }
        }

And then the derived method to TriColorProvider:

        protected override IntPtr GetWindowHandle()
        {
            // Return our window handle, since we're a root provider
            return this.control.Handle;
        }

This technique turns out to be extremely useful when we get to GetPropertyValue().  GetPropertyValue() needs to return the value of whatever property the caller requests.  Much like a window procedure, the body of GetPropertyValue() is a big switch statement that routes various property queries to the code that answers them.  To avoid making GetPropertyValue() complicated and long, we factor the various queries into separate methods.  To be clear, you don’t have to do it this way – you can do the work in the switch statement – but it smells bad to me.  Following our pattern above, we add the base GetPropertyValue() method to BaseSimpleProvider, along with a protected GetName() virtual method.  Once we do that, all that TriColorProvider  has to do to say Hello World! is override the name getter:

        protected override string GetName()
        {
            // This should be localized; it's hard-coded only for the sample.
            return "Hello world!";
        }

We’re almost done.  We have a provider object that provides a complete implementation of IRawElementProviderSimple.  Your code would compile now – but it wouldn’t do anything different.  We still need to hook up our new object.  We’ll add a field to TriColorControl and then a property wrapper that creates the provider on demand (saving memory if we never actually use it):

        private TriColorProvider provider;
        private IRawElementProviderSimple Provider
        {
            get
            {
                if (this.provider == null)
                {
                    this.provider = new TriColorProvider(this);
                }
                return this.provider;
            }
        }

Finally, we need to respond to the WM_GETOBJECT window message by returning our provider.  WM_GETOBJECT is the message requesting the Accessibility provider for an HWND – if you are familiar with MSAA, you’ve seen this before.  In UIA, there is one correct way to implement WM_GETOBJECT, and it looks like this:

            if (m.Msg == 0x3D /* WM_GETOBJECT */)
            {
                m.Result = AutomationInteropProvider.ReturnRawElementProvider(
                    m.HWnd, m.WParam, m.LParam, this.Provider);
            }

You do not need to examine WParam or LParam – just pass them straight through to ReturnRawElementProvider.  This is important to ensure that the UIA-to-MSAA Bridge, which helps MSAA clients to use your UIA object, works properly.  Also note that I’m passing back the same provider object every time.  You don’t have to do this, but creating a new one each time would be wasteful. 

And I’m done.  I can compile and look at my control with Inspect and see my “Hello World!” property coming through:

original[2]

We’ve successfully provided one property!   

Next time: The rest of the basic properties.