Click here to Skip to main content
15,868,016 members
Articles / Programming Languages / C#
Article

UI Automation Using Microsoft Active Accessibility (MSAA)

Rate me:
Please Sign up or sign in to vote.
4.92/5 (46 votes)
11 Aug 2009CPOL4 min read 138.2K   4.9K   58   20
This article describes a new way to automate window applications based on MSAA which otherwise is not possible using any other technique.

Introduction

Microsoft Active Accessibility is Microsoft’s User Interface technology. All window controls which are based on this technology can expose information about the UI elements to the outside world. It is a COM based technology and provides the IAccessible interface. Controls implementing this interface expose certain methods which provide information about UI elements like location, type, state, name, value, role, and the default action which can be invoked. We use this technology to write automation libraries for MS Word 2007, while the UI is completely based on MSAA.

I would like to share the same experience on this forum as it has given us very promising results.

Background

We started developing a Windows UI automation library based on Microsoft UIA. We are using this library as a platform to write automated functional test cases for Windows clients. As this library is based on Microsoft UIA, it works well with Win32 and WinForms based applications, but it doesn’t work with Windows applications based on MSAA (e.g., MS Office 2007). That’s where we extended our UI automation library for MSAA support and we successfully achieved our mission.

Approach

We followed the approach as demonstrated in the diagram below:

Approach.jpg

  • Win32 API consumes MSAA services from the Windows OS.
  • MSAA Layer is a core automation module. It consumes MSAA services from the Win32 API and provides services to search UI elements on the screen and to get the IAccessible interface as a COM object for that UI element. This layer treats each UI element as an AccessibleUIItem. It can be a window, button, textbox, or any control which implements the IAccessible interface. It provides an efficient searching mechanism to search through an accessible tree.
  • UI Manager defines the object model for the application’s UI which we want to automate. It extends the base AssessibleUIItem provided by the MSAA layer. We will see an example of the UI object model designed for MS Office 2007 later in this article.

Now, let's have a look at each layer in detail. A code walkthrough for each layer would be my focus here.

Win32 API

We have developed the automation library in .NET so we used PInvoke to fetch the services of the Win32 API. Following is the set of Win32 APIs we have used:

C#
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);

[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

[DllImport("user32.dll", EntryPoint = "FindWindow", 
           SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr FindWindowByClass(string lpClassName, IntPtr zero);

[DllImport("user32.dll", EntryPoint = "FindWindow", 
      SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr FindWindowByCaption(IntPtr zero, string lpWindowName);

[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern bool EnumChildWindows(IntPtr hWndParent, 
              EnumWindowsProc lpEnumFunc, int lParam);

[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int GetWindowText(IntPtr hwnd, 
                         StringBuilder lpString, int cch);

[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int GetWindowTextLength(IntPtr hwnd);

[DllImport("user32.dll", SetLastError = true)]
public static extern bool BringWindowToTop(IntPtr hwnd);

[DllImport("oleacc.dll")]
public static extern uint GetRoleText(uint dwRole, 
       [Out] StringBuilder lpszRole, uint cchRoleMax);

[DllImport("oleacc.dll")]
public static extern uint GetStateText(uint dwStateBit, 
       [Out] StringBuilder lpszStateBit, uint cchStateBitMax);

[DllImport("oleacc.dll")]
public static extern uint WindowFromAccessibleObject(IAccessible pacc, 
                                                     ref IntPtr phwnd);

[DllImport("oleacc.dll", PreserveSig = false)]
[return: MarshalAs(UnmanagedType.Interface)]
public static extern object AccessibleObjectFromWindow(int hwnd, 
                            int dwId, ref Guid riid);

[DllImport("oleacc.dll")]
public static extern int AccessibleObjectFromWindow(
     IntPtr hwnd,
     uint id,
     ref Guid iid,
     [In, Out, MarshalAs(UnmanagedType.IUnknown)] ref object ppvObject);   

[DllImport("oleacc.dll")]
public static extern int AccessibleChildren(IAccessible paccContainer, 
                         int iChildStart, int cChildren,
                         [Out()] [MarshalAs(UnmanagedType.LPArray, 
                         SizeParamIndex = 4)] object[] rgvarChildren, 
                         ref int pcObtained);

public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

MSAA Layer

This layer provides the abstraction over the Win32 API. Its main job is to search the accessible UI item from the whole Accessibility Tree whose root node represents Desktop. After the item is found, it fetches all its properties and provides the MSAAUIItem object. The search mechanism is very optimized. We can ignore the invisible UI items while searching.

"There is a tool available from Microsoft called Accessibility Explorer (AccExplorer32.exe). Using this tool we can easily traverse through all Accessibility Trees."

MSAAUIItme exposes the following UI item properties:

  • Name
  • Role
  • State
  • Location
  • Value
  • Handle
  • IsEnabled
  • DefaultAction

Approach.jpg

How to Search the UI Item

It is a two step process.

MSAA.cs

  • Step 1: Get the accessible object of the top application window. For example, the "MS Word 2007" window.
  • C#
    public static IAccessible GetTopWindowAccessibleObject(Regex windowName)
    {
        foreach (IAccessible accWindowObject in GetTopWindowAccessibleList())
        {
            try
            {
                string accWindowName = accWindowObject.get_accName(0);
    
                if (!string.IsNullOrEmpty(accWindowName))
                {
                    if (windowName.Match(accWindowObject.get_accName(0)).Success)
                    {
                        return accWindowObject;
                    }
                }
            }
            catch (Exception ex)
            {
            }
        }
        return default(IAccessible);
    }
  • Step 2: Use the top window accessible object as the parent object and search for the child object under that. E.g., "Ribbon" is a child object of the "MS Word 2007" window.
  • C#
    public static IAccessible GetObjectByName(IAccessible objParent, 
                  Regex objName, bool ignoreInvisible)
    {
        IAccessible objToReturn = default(IAccessible);
    
        if (objParent != null)
        {
            IAccessible[] children = GetAccessibleChildren(objParent);
            foreach (IAccessible child in children)
            {
                string childName = null;
                string childState = string.Empty;
    
                try
                {
                    childName = child.get_accName(0);
                    childState = 
                      GetStateText(Convert.ToUInt32(child.get_accState(0)));
                }
                catch (Exception)
                {
                }
    
                if (ignoreInvisible)
                {
                    if (childName != null 
                        && objName.Match(childName).Success
                        && !childState.Contains("invisible"))
                    {
                        return child;
                    }
                }
                else
                {
                    if (childName != null 
                        && objName.Match(childName).Success)
                    {
                        return child;
                    }
                }
                        
                if (ignoreInvisible)
                {
                    if (!childState.Contains("invisible"))
                    {
                        objToReturn = GetObjectByName(child, objName, ignoreInvisible);
                        if (objToReturn != default(IAccessible))
                        {
                            return objToReturn;
                        }
                    }
                }
                else
                {
                    objToReturn = GetObjectByName(child, objName, ignoreInvisible);
                    if (objToReturn != default(IAccessible))
                    {
                        return objToReturn;
                    }
                }
                
            }
        }
        return objToReturn;
    }

How to Get the State

This is a tricky part. IAccessible.get_accState(object varChild) returns a state ID number which is a combination of one or multiple states. E.g., "MS Word 2007" has three states at a particular instance: focusable, moveable, sizeable. But, the Win32 API GetStateText will return only the first state string against the state ID. So, we have to call this API more than once. How we do it is shown in the following code snippet.

MSAAState.cs

C#
public static string GetStateText(uint stateID)
{
    uint maxLength = 1024;
    var focusableStateText = new StringBuilder((int)maxLength);
    var sizeableStateText = new StringBuilder((int)maxLength);
    var moveableStateText = new StringBuilder((int)maxLength);
    var invisibleStateText = new StringBuilder((int)maxLength);

    //This pattern needs to be used to fetch other available cobination of states. 
    if (stateID == (MSAAStateConstants.STATE_SYSTEM_FOCUSABLE
                   | MSAAStateConstants.STATE_SYSTEM_SIZEABLE
                   | MSAAStateConstants.STATE_SYSTEM_MOVEABLE))
    {
        Win32.GetStateText(MSAAStateConstants.STATE_SYSTEM_FOCUSABLE, 
                           focusableStateText, maxLength);
        Win32.GetStateText(MSAAStateConstants.STATE_SYSTEM_SIZEABLE, 
                           sizeableStateText, maxLength);
        Win32.GetStateText(MSAAStateConstants.STATE_SYSTEM_MOVEABLE, 
                           moveableStateText, maxLength);

        return focusableStateText + "," + sizeableStateText + "," + moveableStateText;

    }

    var stateText = new StringBuilder((int)maxLength);
    Win32.GetStateText(stateID, stateText, maxLength);
    return stateText.ToString();
}

MSAAUIItem answers these questions and abstracts out other complexities as well.

Office 2007 UI Automation Object Model

As the name sounds, this layer is specific to the Windows application which needs to be automated. This layer extends MSAAUIItem to define the Office 2007 UI controls.

Office2007UIAModel.jpg

OfficeUIItem extends MSAAUIItem and acts as the core Office UI item. All other items like OfficeRibbon, OfficeToolBar, OfficeRibbonTab, OfficePropertyPage, and MSWordWindow extend OfficeUIItem.

Now, let us write some automation code. This code will launch MS Word 2007 and will open the tab named "Insert". Then, it will run the "Cover Page" command on the "Pages" Tool Bar. It is important to select a particular tab because only then MS Word will refresh the UI and load the corresponding toolbars and controls under that.

C#
Process.Start("winword");
MSWordWindow msWordMainWindow = new MSWordWindow("Document1 -");
msWordMainWindow.SelectTab("Insert");
msWordMainWindow.ToolBars["Pages"].Controls["Cover Page"].Invoke();

Attached Sample Code

The sample code attached with this article is tested on Windows XP SP2 and Windows 2008. The attached code will work for Windows XP but may fail for Windows 2008. The reason for this is that the Win32 API which we use to get the Accessible Object is different for both OSs.

Win32.cs

C#
//Windows XP
[DllImport("oleacc.dll")]
internal static extern int AccessibleObjectFromWindow(
     IntPtr hwnd,
     uint id,
     ref Guid iid,
     [In, Out, MarshalAs(UnmanagedType.IUnknown)] ref object ppvObject);    

//Windows 2008
[DllImport("oleacc.dll", PreserveSig = false)]
[return: MarshalAs(UnmanagedType.Interface)]
public static extern object AccessibleObjectFromWindow(int hwnd, 
                            int dwId, ref Guid riid);

Conclusion

Microsoft Active Accessibility is not a comprehensive solution to automate the Windows UI. There are certain limitations. E.g., using accessibility, we can search for a TextBox control on MS Word Ribbon, but we can not get or set a value in it, which can be done using Microsoft UIA. So, a combination of MSAA and UIA can provide a more promising solution for Windows UI automation.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Proteans Software Solutions
India India
Currently, I am working with Proteans Software Solutions

Proteans a CAMO group company is an outsourcing company focusing on software product development and business application development on Microsoft Technology Platform. "Committed to consistently deliver high-quality software products and services through continual improvement of our knowledge and practices focused on increased customer satisfaction.

Before this, I started my Development career with Quark Media House Pvt. Ltd.

I have worked in various domains like Publishing, Document Management and Health Care.

I have gained experience in Development, Debugging, Bug fixing, Memory leak fixing and Performance Consultancy.

I am working on .NET technologies.

Comments and Discussions

 
QuestionHidden controls Pin
Member 87868013-Aug-17 14:39
Member 87868013-Aug-17 14:39 
QuestionValuable informaiton Pin
rnvc3-Feb-17 2:07
rnvc3-Feb-17 2:07 
QuestionNeed help for steps to access VCL controls as MSAA controls for Coded UI Pin
Member 134597326-Jul-14 6:52
Member 134597326-Jul-14 6:52 
QuestionWorking with Office 2010 Pin
Madhukar_CodeP27-Feb-14 20:14
Madhukar_CodeP27-Feb-14 20:14 
Questioncan not download Pin
justpower27-Apr-13 0:38
justpower27-Apr-13 0:38 
GeneralMy vote of 5 Pin
ShlomiO19-Nov-12 22:32
ShlomiO19-Nov-12 22:32 
Ali, this is a very good article, it helped me a lot, 10x!!
QuestionIt seems to me like the MSAARoles class is out of date Pin
Chris Robert Mead22-May-12 11:57
Chris Robert Mead22-May-12 11:57 
GeneralMy vote of 5 Pin
Abinash Bishoyi5-Feb-12 5:55
Abinash Bishoyi5-Feb-12 5:55 
QuestionUI Automation- How to click on text Pin
smashgeek28-Apr-11 21:38
smashgeek28-Apr-11 21:38 
GeneralWin 7 / VS 2010 Pin
jharrop@gmail.com18-Apr-10 22:31
jharrop@gmail.com18-Apr-10 22:31 
GeneralI really liked the topic Pin
manu.arora17-Aug-09 4:20
manu.arora17-Aug-09 4:20 
GeneralThis is great but... Pin
NideshTheDeveloper16-Aug-09 16:56
NideshTheDeveloper16-Aug-09 16:56 
GeneralRe: This is great but... Pin
Arshad_Ali_alizproarts16-Aug-09 19:06
Arshad_Ali_alizproarts16-Aug-09 19:06 
GeneralHanging on to old controls Pin
BuckKenn14-Aug-09 11:54
BuckKenn14-Aug-09 11:54 
GeneralRe: Hanging on to old controls Pin
Arshad_Ali_alizproarts14-Aug-09 21:17
Arshad_Ali_alizproarts14-Aug-09 21:17 
GeneralRe: Hanging on to old controls Pin
BuckKenn19-Aug-09 12:15
BuckKenn19-Aug-09 12:15 
GeneralRe: Hanging on to old controls Pin
BuckKenn20-Aug-09 12:57
BuckKenn20-Aug-09 12:57 
GeneralRe: Hanging on to old controls Pin
Arshad_Ali_alizproarts20-Aug-09 18:29
Arshad_Ali_alizproarts20-Aug-09 18:29 
GeneralRe: Hanging on to old controls Pin
BuckKenn20-Aug-09 19:31
BuckKenn20-Aug-09 19:31 
GeneralRe: Hanging on to old controls Pin
Arshad_Ali_alizproarts21-Aug-09 2:57
Arshad_Ali_alizproarts21-Aug-09 2:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.