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
IRawElementProviderFragment,
IRawElementProviderFragmentRoot
{
// SendMessage communicates between client side provider and the program using MFCGridCtrl
[System.Runtime.InteropServices.DllImport("user32.dll")]
[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;
private IntPtr providerHwnd;
public int SendMessage(IntPtr wParam, IntPtr lParam)
{
return SendMessage(providerHwnd, WM_USER, wParam, 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()
{
....
}
}
}
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