Tuesday, July 11, 2006

Creating a UI Automation Provider for a .Net Control

In this post, I will give an overview of the process of creating a client side UI Automation Provider for the MenuStrip Control. This will focos on how to get access to the data, not how to do each detailed step.

  1. Create the Provider Assembly
  2. Get a MenuStrip reference from the Windows Handle of the Control
  3. Implement the MenuStrip Provider using the MenuStrip object
  4. Register the Provider


Create the Provider Assembly
  1. Create a project with any name
  2. Add a static class called UIAutomationClientSideProviders
  3. Add a static field called ClientSideProviderDescriptionTable which is a ClientSideProviderDescription[]
  4. For each provider add a ClientSideProviderDescription to the array with a delegate used to create the provider and the UI "class name" of the control. The UI "class name" can be found with the UISpy tool or Spy++.
The code for a MenuStrip is below:

using System.Windows.Automation;
using System.Windows.Automation.Provider;

// The namespace must be the same as the name of the DLL,
// so that UI Automation can find the table of descriptors.
// In this example, the DLL would be "Auxsr.CustomClientSideProviders.dll"
namespace Auxsr.CustomClientSideProviders
{
// The assembly must implement a UIAutomationClientSideProviders class
public static class UIAutomationClientSideProviders
{
// Implementation of the static ClientSideProviderDescriptionTable field.
// In this case, only a single provider is listed in the table.
public static ClientSideProviderDescription[] ClientSideProviderDescriptionTable =
{
new ClientSideProviderDescription(
new ClientSideProviderFactoryCallback(MenuStripProvider.Create),
"WindowsForms10.Window.8.app.0.378734a")
};
}

}



Get a Control reference from the Windows Handle of the Control
  1. The ClientSideProviderFactoryCallback method takes a windows handle as the first parameter.
  2. Call the Control.FromHandle method to get a reference to the MenuStrip.
  3. Try to cast the Control to a MenuStrip reference
  4. If it succeded, you now have acces to the MenuStrip object

internal static IRawElementProviderSimple Create(
IntPtr hwnd, int idChild, int idObject )
{
// Get a reference to the managed control from the windows handle
Control controlFromHwnd = Control.FromHandle( hwnd );

// If the control is a MenuStrip
if( controlFromHwnd is MenuStrip )
{
// Create a menu strip provider
return new MenuStripProvider( controlFromHwnd as MenuStrip );
}
// Otherwise, return the default provider
else
{
return AutomationInteropProvider.HostProviderFromHandle( hwnd );
}
}


Implement the MenuStrip Provider

Implement the following interfaces using the MenuStrip object:
IRawElementProviderSimple
IRawElementProviderFragment
IRawElementProviderFragmentRoot

See examples in the Windows SDK by searching for each of the previous interfaces.


Register the Provider


In the client, register the provider assembly at runtime.

ClientSettings.RegisterClientSideProviderAssembly(
typeof(
Auxsr.CustomClientSideProviders.UIAutomationClientSideProviders
).Assembly.GetName() );


As an alternative, a client could automatically load all the providers assemblies from a certain directory on the hard drive. This could also be made secure by using an AppDomain with limited permissions. I have not tested this, but I believe it would be the optimal solution for accessibility technology in order to extend the client with additional client side providers without actually modifying the client.

9 Comments:

Anonymous Anonymous said...

Is UI Automation in the works for XP too? When will it be released?

4:46 AM  
Anonymous Anonymous said...

Hi guys,

Thanks for the post. I'm facing a problem when trying to replicate your example. I'm having a target app with a menu strip and another "client" app that has the MenuStripProvider code. Therefore, I need the MenuStrip object to be available in the client app given its handle and class name in runtime -- however, this will no work because these are 2 different processes and the .NET control instance of the menu strip cannot be retrieved in the client application. So how can we proceed with this whole client side implementation approach?

Thanks in advance!

8:42 AM  
Anonymous Anonymous said...

Hi Rick,

How do you handle out-of-process client side providers trying to get at the MenuStrip control instance given just its Win32 handle?

8:39 AM  
Blogger ivan said...

I reach this posting while searching on the solution for UI automation windows form menu strip and link from http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=460545&SiteID=1
Can you provide me some guideline or sample source code. As I still don't understand on your posting. Thanks.

5:32 AM  
Blogger Unknown said...

I did everything what is listed here.
But Control.FromHandle() method always returns null instead control reference for all my win form controls (handles that passed to IRawElementProviderSimple.Create method are correspond to my win forms controls). Thanks a lot for any help/response/working project.

3:10 AM  
Blogger Rick said...

Anonymous:
UIAutomation is part of .net 3.0 and it is compatible with Win XP. However, I have not worked with it in over a year, so I am not sure what the state of it is.

10:34 PM  
Blogger Rick said...

Chai:

You obtain the Window handle from the UIAutomation tree. Read up on the automation tree at MSDN to get a good understanding of it. This link should get you started:
http://msdn2.microsoft.com/en-us/library/ms741931.aspx

10:38 PM  
Blogger Rick said...

Ivan:

Sadly I have lost all the source code from a hard drive crash about a year ago. I've also been out of touch with this technology for a while...

10:40 PM  
Blogger Rick said...

Vladimir:

I'm not sure what might be the problem. You might try getting a window handle from the automation tree for other objects to verify the source of the problem.

10:47 PM  

Post a Comment

<< Home