UIA Custom Patterns: Part 2


In part 1, I talked about how to create and use custom properties in UI Automation.  I want to move on now to custom patterns.  In UIA, a pattern (or more formally, a control pattern) is a bundle of properties, methods, and events that go together: a control usually exposes the whole set, or none of them.  The Value pattern is a good example: it is the combination of a Value property, a SetValue method, and a ValueChanged event.  Custom patterns end up being significantly more complicated that custom properties – that’s why this is a multi-part series.  In this part, we’ll talk about defining interfaces and implementing a basic method.  The samples for this post are here

As I noted before, the MSDN walkthrough on this topic is good, especially if you are working in native code.  The MSDN walkthrough uses a variation of the Value pattern, but just for fun, I’m going to choose a different example I made up: the Color Pattern.  The Color Pattern allows my Tri-Color Control to expose its value as an RGB color, rather than as a string.  This might be convenient if I want to know the exact color or if the color names change in various languages and I want a language-independent value. 

Defining our interfaces

The first thing to note is that the provider and the client are going to see this pattern differently.  You can see this difference in all of the UIA patterns: for instance, a provider implements IValueProvider, but a client uses IUIAutomationValuePattern.  Custom patterns are similar, which is why we’ll need to define two different COM interfaces: one for the provider, and one for the client.  It is usually easier to do the provider first.

Here’s the provider interface definition for the Color Pattern:

    // Declaration of the provider-side interface, which the control will implement.
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [ComImport()]
    [Guid("49F2F4CD-FFB7-4b21-9C4F-58090CDD8BCE")]
    public interface IColorProvider
    {
        int ValueAsColor { get; }
        void SetValueAsColor(int value);
    }

I need to use the appropriate .NET attributes to mark this as a COM interface and specific its interface GUID.  (I used the GUID Generator in the SDK to create all of my GUIDs.)  My single property, ValueAsColor, is defined here, and so is my single method, SetValueAsColor.  They are typed as integers.  I can use any types I want, but UI Automation supports five basic data types (int, string, bool, double, and Element), so I’ll need to be able to convert my type to one of those eventually. 

My client interface definition is going to be similar, but it will define “Current” and “Cached” versions of the property, so that the client can use UI Automation property caching for improved performance if desired.  This is almost a mechanical transformation.  I suppose if you were doing a lot of these, you could write a script to convert provider interfaces to client interfaces.  But, in the end, you will be implementing both interfaces, so you can define them with a different naming style from mine if you wish.  Here’s what I did for the client pattern:

    // Declaration of the client-side interface, for the client/test to use.
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [ComImport()]
    [Guid("B98D615C-C7A2-4afd-AEC9-62FF4501AA30")]
    public interface IColorPattern
    {
        int CurrentValueAsColor { get; }
        int CachedValueAsColor { get; }
        void SetValueAsColor(int value);
    }

Here’s a great diagram, linked from MSDN, that shows how these all relate to each other. 

 

  • The client interface ends up being a front-end Adapter to the IUIAutomationPatternInstance object.  It is responsible for taking the client calls and translating them into the ‘language’ that UIA can understand.
  • A pattern handler object does the opposite translation on the provider side: it takes the UIA ‘language’ and translates it back into method calls to the provider interface.
  • Your UI element implements the provider interface.

Therefore, for each interface method, I actually have three implementations to build:

  • The client pattern instance implementation of the method
  • The pattern handler dispatcher implementation of the method
  • The UI element’s implementation of the method

 

A Client Pattern Instance Method

Starting from the client end: here’s my implementation of the client pattern instance version of SetValueAsColor:

        public void SetValueAsColor(int value)
        {
            CallMethod(ColorSchema.GetInstance().SetValueAsColorMethod, value);
        }

This isn’t a great sample.  The problem is, I wrote enough samples side by side that I started to see the common relationships between them, and many of the client pattern instance calls had the same structure: package up the in-parameters and then tell UIA to make the custom method call to the provider.  So I factored that functionality into a helper method, CallMethod().  For educational purposes, if I un-refactored it, it would look like this:

        public void SetValueAsColor(int value)
        {
            // Create and init a parameter list
            UiaParameterListHelper paramList = new UiaParameterListHelper(
ColorSchema.GetInstance().SetValueAsColorMethod); paramList[0] = value; // Call through this.patternInstance.CallMethod(
ColorSchema.GetInstance().SetValueAsColorMethod.Index,
paramList.Data, paramList.Count); }

This captures better what the client pattern instance has to do: set up an array of UIAutomationParameter structures, initialize them with the in-parameter data, call through to the provider, and then reverse the process to unpack any out-parameters.

To save you some time on a point that confused me: the index is an integer that identifies the method among the other pattern methods.  If you register a set of X properties and Y methods as a pattern, then the indices from 0 to (X-1) will be the property indices, and the indices from X to (Y-1) will be the method indices.  In the case of Color Pattern, ValueAsColor has index 0 and SetValueAsColor has index 1.  I could have hard-coded the index 1 in this case, but since it might change if I ever add more methods, I thought it was cleaner to store it as part of my schema. 

As I noted in part 1, I created a Singleton ‘schema’ class for each pattern to be responsible for registration and storing data for each pattern.  That’s what ColorSchema is, and it is a convenient place to store method and property indices, too.

Dispatching on the Provider Side

There has to be code on the provider side to do the opposite side of this job.  The whole process is reversed: UIA shows up with an index and a block of data, and you get to unpack the data into parameters, call the right method, and then re-pack any out parameters.  There’s a single method that you implement, called Dispatch, that does this job.  Here’s the relevant parts of Dispatch for Color Pattern:

        public void Dispatch(object pTarget, uint index, IntPtr pParams, uint cParams)
        {
            // Parse the provider and parameter list
            IColorProvider provider = (IColorProvider)pTarget;
            UiaParameterListHelper paramList = 
new UiaParameterListHelper(pParams, cParams); // Dispatch the method/property calls if (index == ColorSchema.GetInstance().ValueAsColorProperty.Index) { paramList[0] = provider.ValueAsColor; } else if (index == ColorSchema.GetInstance().SetValueAsColorMethod.Index) { provider.SetValueAsColor((int)paramList[0]); } else { throw new InvalidOperationException(); } }

As I noted earlier, I factored out common functionality when it made sense.  In this case, a UiaParameterListHelper class is responsible for taking the block of parameter data and splitting it out into parameters, which I can then just cast to the correct type and call into my method.  SetValueAsColor has no out-parameters, which simplifies matters, but you can see how they work in the dispatcher for the ValueAsColorProperty (more on pattern properties in the next part of this series.)

The Provider Method Itself

The last piece is to implement the provider method itself on the UI Control.  I added the IColorProvider interface to the list of interfaces that my TriColorProvider derives from and implemented all of its methods.  This is just like implementing any other provider interface in UIA.  Here’s how  the SetValueAsColor method looks – you can see that this code has no idea that it’s part of a custom pattern:

        void IColorProvider.SetValueAsColor(int value)
        {
            bool found = false;

            TriColorValue newValue = TriColorValue.Red;
            if (value == (System.Drawing.Color.Red.ToArgb() & 0xFFFFFF))
            {
                newValue = TriColorValue.Red;
                found = true;
            }
            else if (value == (System.Drawing.Color.Yellow.ToArgb() & 0xFFFFFF))
            {
                newValue = TriColorValue.Yellow;
                found = true;
            }
            else if (value == (System.Drawing.Color.Green.ToArgb() & 0xFFFFFF))
            {
                newValue = TriColorValue.Green;
                found = true;
            }
            if (found)
            {
                this.control.Value = newValue;
            }
        }

To re-emphasize the point: the implementation of the provider methods and properties on the UI Automation provider for the UI element isn’t “custom” at all.  The method makes no reference to the UI Automation Registrar or even to my ColorSchema singleton.  All of the “custom-ness” is encapsulated in the client pattern instance and in the method dispatcher.  Which, I think, is fairly elegant.

I can’t really end this post on a triumphant note, because there isn’t enough here yet to compile and use.  We still haven’t gotten to the objects that contain the client pattern instance methods and dispatch method, or how to register them.  If you want to look ahead, you can check out the full sample and see where this is going.  But this method handling work is really the heart of setting up custom patterns, so the next part will just be about packaging it up.  Stay tuned.


Comments (0)

Skip to main content