Tuesday, January 27, 2015

About Microsoft UI-Automation

UI Automation Client Side Provider

A provider extracts information from a specific control and hands that information to UI Automation. Providers can exist either on the server side or on the client side. In this article, we introduce a mix method, which is mainly implemented on client side but also a small piece of code is added on server side in order to provide more useful functions.

1. Define an Register Provider

public class UIAutomationClientSideProviders
{
    public static ClientSideProviderDescription[]  ClientSideProviderDescTable = 
    {
        new ClientSideProviderDescription(
            GridProvider.Create,      // Method that creates the provider object.
            "MFCGridCtrl")               // Class of window that will be served by the provider.
    };
}

ClientSettings.RegisterClientSideProviders(UIAutomationClientSideProviders.ClientSideProviderDescTable);

2. Implement the Provider

In this example, we create a provider for custom defined UI-component MFCGrifCtrl.

class GridProvider : IRawElementProviderSimple
                                IRawElementProviderFragment
                                IRawElementProviderFragmentRoot
{

    //  SendMessage communicates between client side provider and the program using MFCGridCtrl 
    [System.Runtime.InteropServices.DllImport("user32.dll")]
  public static extern int SendMessage(IntPtr hWnd, uint wMsg, IntPtr wParam, IntPtr lParam);

    const int WM_USER = 0x0400;
    private IntPtr providerHwnd;
    public int SendMessage(IntPtr wParam, IntPtr lParam)
    {
        return SendMessage(providerHwnd, WM_USER, wParam, lParam);
    }

    // self-defined message code for communication with MFCGridCtrl  
    public const int GET_GRID_ROWS = 1;
    public const int GET_GRID_COLS = 2;

    public int rows = 0;
    public int cols = 0;
    public GridCellProvider[] cellArray = null;


    internal static IRawElementProviderSimple Create(IntPtr hwnd, int idChild, int idObject)
    {
        return Create(hwnd, idChild);
    }

    private static IRawElementProviderSimple Create(IntPtr hwnd, int idChild)
    {
        if (idChild != 0) return null;
        return new GridProvider(hwnd);
    }

    public GridProvider(IntPtr hwnd)
    {
        providerHwnd = hwnd;
        // get related information from MFCGrifCtrl
        rows = SendMessage((IntPtr)GET_GRID_ROWS, IntPtr.Zero);
        cols = SendMessage((IntPtr)GET_GRID_COLS, IntPtr.Zero);
        cellArray = new GridCellProvider[rows*cols];
    }

    // The followings are IRawElementProviderSimple methods (total 4)
    ProviderOptions IRawElementProviderSimple.ProviderOptions
    {
        get { return ProviderOptions.ClientSideProvider; }
    }
    IRawElementProviderSimple IRawElementProviderSimple.HostRawElementProvider
    {
       get { return null; }
    }
    object IRawElementProviderSimple.GetPropertyValue(int aid)
    {
        if (property == AutomationElementIdentifiers.HelpTextProperty)
        {
            return rows + "," + cols + " (rows,columns)";
        }
        return null;
     }
     object IRawElementProviderSimple.GetPatternProvider(int iid)
     {
         return null;
     }

     // The followings IRawElementProviderFragment methods (total 6)
     public Rect BoundingRectangle
     {
            get { return rect; }
     }
     public IRawElementProviderFragmentRoot FragmentRoot
     {
         get { return this; }
     }
     public IRawElementProviderSimple[] GetEmbeddedFragmentRoots()
     {
         return null;
     }
     public void SetFocus()
     {
         return;
     }
     public int[] GetRuntimeId()
     {
         return null;
     }

     public IRawElementProviderFragment Navigate(NavigateDirection direction)
     {
         switch (direction)
         {
              case NavigateDirection.FirstChild:
                   if (cellArray[0] == null) cellArray[0] = new GridCellProvider(this, 0, 0);
                   return (IRawElementProviderFragment)cellArray[0];
                   break;
         }
         return null;
    }

    // The followings are IRawElementProviderFragmenRoot methods (total 2)
    public IRawElementProviderFragment ElementProviderFromPoint(double x, double y)
    {
        return null;
    }
    public IRawElementProviderFragment GetFocus()
    {
        return null;
    }
}

MFCGridCtrl Grid Cell Provider

class GridCellProvider : IRawElementProviderSimple, 
                                       IRawElementProviderFragment, 
                                       IInvokeProvider
{
    private GridProvider parent;
    private int rowId;
    private int colId;

    public GridCellProvider(GridProvider parent, int row, int col)
    {
         columnId = col;
         rowId = row;
         this.parent = parent;
     }


    // IRawElementProviderSimple methods (total 4)
    ProviderOptions IRawElementProviderSimple.ProviderOptions
    {
        get { return ProviderOptions.ClientSideProvider; }
    }
    IRawElementProviderSimple IRawElementProviderSimple.HostRawElementProvider
    {
        get { return null; }
    }
    object IRawElementProviderSimple.GetPropertyValue(int aid)
    {
        var property = AutomationProperty.LookupById(aid);
        if (property == AutomationElementIdentifiers.ClassNameProperty)
        {
            return "Cell" + rowId + "." + columnId;
        }
        else if (property == AutomationElementIdentifiers.AutomationIdProperty)
        {
            return "#" + rowId + "." + columnId;
        }
        else if (property == AutomationElementIdentifiers.NameProperty)
        {
            return "...";
        }
        else if (property == AutomationElementIdentifiers.HelpTextProperty)
        {
            return  "...";
        }
        return null;
    }
    object IRawElementProviderSimple.GetPatternProvider(int iid)
    {
        if (iid == InvokePatternIdentifiers.Pattern.Id)
        {
            // Return an object that implements IInvokeProvider.
            return this;
        }
        else
        {
            return null;
        }
    }

    // IRawElementProviderFragment methods
    public Rect BoundingRectangle
    {
        get { return rect; }
    }
    public IRawElementProviderFragmentRoot FragmentRoot
    {
        get { return parent; }
    }
    public IRawElementProviderSimple[] GetEmbeddedFragmentRoots()
    {
        return null;
    }
    public void SetFocus()
    {
        return;
    }
    public int[] GetRuntimeId()
    {
     return new int[] { AutomationInteropProvider.AppendRuntimeId, (rowId << 16) + columnId };
    }
    public IRawElementProviderFragment Navigate(NavigateDirection direction)
    {
        switch (direction)
        {
            case NavigateDirection.Parent:
                return parent;
            case NavigateDirection.NextSibling:
           if (rowId + 1 < parent.rows || rowId + 1 == parent.rows && columnId + 1 < parent.cols)
                {
                    int next = rowId * parent.cols + columnId + 1;
                    if (parent.cellArray[next] == null)
                        parent.cellArray[next] = new GridCellProvider(parent, next / parent.cols, next % parent.cols);
                    return parent.cellArray[next];
                }
                break;
            case NavigateDirection.PreviousSibling:
                break;
            }
            return null;
        }

        public void Invoke()
        {
             ....
        }
    }

}


3. Modify the Class in Source Code

GridCtrl.h


private: std::string cellText;                  // SLX@Dec03'12 (1 of 2)

       afx_msg LRESULT OnUserMsg(WPARAM wParam, LPARAM lParam);             // SLX@Dec03'14 (2 of 2)


GridCtrl.cpp
#include "GridCellCheck.h"                         // SLX@Dec04'12      (1 of 3)


ON_MESSAGE(WM_USER,OnUserMsg)                      // SLX@Dec03'14 (2 of 3)

// SLX@Dec03'14 (3 of 3) - begin
LRESULT CGridCtrl::OnUserMsg(WPARAM wParam, LPARAM lParam)
{
       if (wParam==0)
       {
              if (lParamreturn (int)cellText[lParam];
                     return 0;
       }

       if (wParam==1) return GetRowCount();
       if (wParam==2) return GetColumnCount();

       int row = (lParam>>16);
       int col = (lParam&0xffff);
       CGridCellBase * cell = GetCell(row,col);

       switch (wParam)
       {
              case 3:                    // Form the cell text and return its length
                     if (cell!=NULL) cellText = cell->GetText();
                     else cellText="";
                     return cellText.length();
              case 4:                    // Is CGridCellCheck ?
                     if (cell==NULL) return -1;
                     if (dynamic_cast(cell) !=0) return 1;
                     return 0;
              case 10:             // Cell left
              case 11:             // Cell top
                     {
                           POINT p;
                           if (!GetCellOrigin(row,col, &p)) return -1;
                             CRect rect;
                             ClientToScreen(rect);
                           if (wParam==10) return p.x+rect.left;
                           else return p.y+rect.top;
                     }
              case 12:             // Cell width
                     return GetColumnWidth(col);
              case 13:             // Cell height
                     return GetRowHeight(row);

              case 40:             // IsCheck ?
              case 41:             // Check
              case 42:             // Uncheck
                     if (cell==NULL) return -1;
                     if (dynamic_cast(cell) !=0)
                     {
                           if (wParam==40) return ((CGridCellCheck*)cell)->GetCheck();
                           if (wParam==41) return ((CGridCellCheck*)cell)->SetCheck(1);
                           if (wParam==42) return ((CGridCellCheck*)cell)->SetCheck(0);
                     }
                     return -1;
       }
    return 0;
}
// SLX@Dec03'14 (3 of 3) - end


Followers