4

In the image below there is an area, which has an unknown (custom) class. That's not a Grid or a Table.

enter image description here

I need to be able:

  • to select Rows in this area
  • to grab a Value from each cell

The problem is since that's not a common type element - I have no idea how to google this problem or solve it myself. So far the code is following:

Process[] proc = Process.GetProcessesByName("programname");
AutomationElement window = AutomationElement.FromHandle(proc [0].MainWindowHandle);
PropertyCondition xEllist2 = new PropertyCondition(AutomationElement.ClassNameProperty, "CustomListClass", PropertyConditionFlags.IgnoreCase);
AutomationElement targetElement = window.FindFirst(TreeScope.Children, xEllist2);

I've already tried to threat this Area as a textbox, as a grid, as a combobox, but nothing solved my problem so far. Does anybody have any advice how to grab data from this area and iterate through rows?

EDIT: sorry I've made a wrong assumption. Actually, the header(column 1, column 2, column 3) and the "lower half" of this area are different control-types!!

Thanks to Wininspector I was able to dig more information regarding these control types:

  • The header has following properties: HeaderControl 0x056407DC (90441692) Atom: #43288 0xFFFFFFFF (-1)
  • and the lower half has these: ListControl 0x056408A4 (90441892) Atom: #43288 0x02A6FDA0 (44498336)

The code that I've showed earlier - retrieved the "List" element only, so here is the update:

Process[] proc = Process.GetProcessesByName("programname");
AutomationElement window = AutomationElement.FromHandle(proc [0].MainWindowHandle);
//getting the header
PropertyCondition xEllist3 = new PropertyCondition(AutomationElement.ClassNameProperty, "CustomHeaderClass", PropertyConditionFlags.IgnoreCase);
AutomationElement headerEl = XElAE.FindFirst(TreeScope.Children, xEllist3);
//getting the list
PropertyCondition xEllist2 = new PropertyCondition(AutomationElement.ClassNameProperty, "CustomListClass", PropertyConditionFlags.IgnoreCase);
AutomationElement targetElement = window.FindFirst(TreeScope.Children, xEllist2);

After giving it a further thought I've tried to get all column names:

AutomationElementCollection headerLines = headerEl.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.HeaderItem));
string headertest = headerLines[0].GetCurrentPropertyValue(AutomationElement.NameProperty) as string;
textBox2.AppendText("Header 1: " + headertest + Environment.NewLine);

Unfortunately in debug mode element count in "headerLines" is 0 so the program throws an error.

Edit 2: Thanks to the answer below - I've installed Unmanaged UI Automation, which holds better possibilities than the default UIA. http://uiacomwrapper.codeplex.com/ How do you use the legacy pattern to grab data from unknown control-type?

if((bool)datagrid.GetCurrentPropertyValue(AutomationElementIdentifiers.IsLegacyIAccessiblePatternAvailableProperty))
{
var pattern = ((LegacyIAccessiblePattern)datagrid.GetCurrentPattern(LegacyIAccessiblePattern.Pattern));
var state = pattern.Current.State;
}

Edit 3. IUIAutoamtion approach (non-working as of now)

        _automation = new CUIAutomation();
        cacheRequest = _automation.CreateCacheRequest();
        cacheRequest.AddPattern(UiaConstants.UIA_LegacyIAccessiblePatternId);
        cacheRequest.AddProperty(UiaConstants.UIA_LegacyIAccessibleNamePropertyId);
        cacheRequest.TreeFilter = _automation.ContentViewCondition;
        trueCondition = _automation.CreateTrueCondition();


        Process[] ps = Process.GetProcessesByName("program");
        IntPtr hwnd = ps[0].MainWindowHandle;
        IUIAutomationElement elementMailAppWindow = _automation.ElementFromHandle(hwnd);


        List<IntPtr> ls = new List<IntPtr>();

        ls = GetChildWindows(hwnd);

        foreach (var child in ls)
        {
            IUIAutomationElement iuiae = _automation.ElementFromHandle(child);
            if (iuiae.CurrentClassName == "CustomListClass")
            {
                var outerArayOfStuff = iuiae.FindAllBuildCache(interop.UIAutomationCore.TreeScope.TreeScope_Children, trueCondition, cacheRequest.Clone());
                var outerArayOfStuff2 = iuiae.FindAll(interop.UIAutomationCore.TreeScope.TreeScope_Children, trueCondition);

                var countOuter = outerArayOfStuff.Length;
                var countOuter2 = outerArayOfStuff2.Length;

                var uiAutomationElement = outerArayOfStuff.GetElement(0); // error
                var uiAutomationElement2 = outerArayOfStuff2.GetElement(0); // error
    //...
    //I've erased what's followed next because the code isn't working even now..
              }
        }

The code was implemented thanks to this issue:

Read cell Items from data grid in SysListView32 of another application using C#

As the result:

  • countOuter and countOuter2 lengths = 0
  • impossible to select elements (rows from list)
  • impossible to get ANY value
  • nothing is working
Community
  • 1
  • 1
Alex
  • 4,607
  • 9
  • 61
  • 99
  • What do mean "unknown (custom) class"? This is not your code? It looks like a listview.. – banging Jun 22 '12 at 20:06
  • 1
    1. Why would I use UIAutomation on my own application? 2. "unknown (custom) class" equals "unknown control-type". 3. "This is not your code?" - exactly (see p.1). – Alex Jun 23 '12 at 08:26

1 Answers1

2

You might want to try using the core UI automation classes. It requires that you import the dll to use it in C#. Add this to your pre-build event (or do it just once, etc):

"%PROGRAMFILES%\Microsoft SDKs\Windows\v7.0A\bin\tlbimp.exe" %windir%\system32\UIAutomationCore.dll /out:..\interop.UIAutomationCore.dll"

You can then use the IUIAutomationLegacyIAccessiblePattern.

Get the constants that you need for the calls from:

C:\Program Files\Microsoft SDKs\Windows\v7.1\Include\UIAutomationClient.h

I am able to read Infragistics Ultragrids this way.

If that is too painful, try using MSAA. I used this project as a starting point with MSAA before converting to all UIA Core: MSSA Sample Code

----- Edited on 6/25/12 ------

I would definitely say that finding the proper 'identifiers' is the most painful part of using the MS UIAutomation stuff. What has helped me very much is to create a simple form application that I can use as 'location recorder'. Essentially, all you need are two things:

  • a way to hold focus even when you are off of your form's window Holding focus

  • a call to ElementFromPoint() using the x,y coordinates of where the mouse is. There is an implementation of this in the CUIAutomation class.

I use the CTRL button to tell my app to grab the mouse coordinates (System.Windows.Forms.Cursor.Position). I then get the element from the point and recursively get the element's parent until I reach the the desktop.

        var desktop = auto.GetRootElement();
        var walker = GetRawTreeWalker();
        while (true)
        {
            element = walker.GetParentElement(element);
            if (auto.CompareElements(desktop, element) == 1){ break;}
        }

----- edit on 6/26/12 -----

Once you can recursively find automation identifiers and/or names, you can rather easily modify the code here: http://blog.functionalfun.net/2009/06/introduction-to-ui-automation-with.html to be used with the Core UI Automation classes. This will allow you to build up a string as you recurse which can be used to identify a control nested in an application with an XPath style syntax.

chrismead
  • 2,163
  • 3
  • 24
  • 36
  • Have you tried using unmanaged UI Automation? I got the LegacyPattern using "UI Automation COM-to-.NET Adapter" but I don't understand what's the next step should be.. (see edit) – Alex Jun 23 '12 at 16:38
  • I've used your IUIAutomation approach and managed to get the legacy pattern for this "list" area. What do I have to do next? How to select a row and grab data from it? var legacyPattern = dataF.GetCurrentPattern(UiaConstants.UIA_LegacyIAccessiblePatternId) as IUIAutomationLegacyIAccessiblePattern; – Alex Jun 24 '12 at 19:52
  • the length of "var outerArayOfStuff = datagrid.FindAllBuildCache(interop.UIAutomationCore.TreeScope.TreeScope_Children, trueCondition, cacheRequest.Clone());" is 0, the values, names(cached, current) are "".. I can't even select a single row. ;( – Alex Jun 24 '12 at 21:04
  • You might want to post how you built your cache request. Also, try using FindAll (I think that is what it is called) instead of FindAllBuildCache to start with. I had performance issues to work through, otherwise I wouldn't have used the cache. I don't have any code in front of me right now. I can add more if you need it. – chrismead Jun 25 '12 at 01:59
  • To tell you the truth - I've used almost every available method there is.. (FindAll, FindFirst, FindAllBuildCache). I've also used three different approaches (Managed UIA, Unmanaged UIA, IUIA). Unfortunately, nothing seems to be working so far. Anyway, I've updated my post with the latest full code (see Edit 3). Thank you for the responses! – Alex Jun 25 '12 at 12:01
  • "**I then get the element from the point and recursively get the element's parent until I reach the the desktop**" isn't it the opposite of what I want to achieve? I know the parent of my "CustomViewElement" What I don't know is the child (Row, which is inaccessible) – Alex Jun 26 '12 at 16:24
  • What hopefully will happen is that you will discover some unexpected nesting of controls. In order to try this out faster, download this tool: [Point Position](http://www.snapfiles.com/downloads/pointpos/dlpointpos.html). Put the coordinates that you get pointing just in the middle of a cell into some test code as see if you can get a IUIAutomationElement back. If you do, then you are in good shape. If you do not, you may have a problem on your hands. – chrismead Jun 26 '12 at 19:37
  • I'v tried everything.. there are no "childs" beneath this control. Tried to use: Inspector, Winspector Spy, Spy++, Winspy++, Managed/Unmanaged UIAutomation Tree.Walker, etc.. There is absolutely NO evidence of "smaller controls".. @chrismead I've made the same program as you've suggested. I can now grab the whole control tree (control -> to Desktop), but this program doesn't help me at all.. Maybe you can help if I tell the the name of the program and give the installation instructions, so you can try and "grab" data from the element yourself? sorry, that's a lot to ask.. – Alex Jul 01 '12 at 12:27
  • First - it's totally OK if you decline the offer! Here are instructions: The program's name is Pokerstars client. You can download it via the official website (unless you are from the US). When done, just install it and launch.. That's all. In the lobby you will see two "list" controls that I need to grab the data from! List Nr. 1 is the list where tables are located. List Nr. 2 is located in the right column and contains player names. Please inform me you had any luck grabbing the data from these lists.. i would be very grateful to you for any help! – Alex Jul 01 '12 at 12:36
  • Ouch, I see what you mean. That application seems rather impenetrable past about two layers deep. I never saw the grid because I couldn't connect the network, but I think that I basically saw the issue you are facing. Are you working with the developers of the application? If so, you may want to ask them to test out a bit of the provider stuff for you: http://msdn.microsoft.com/en-us/library/windows/desktop/ee671597(v=vs.85).aspx I haven't had to do this, but it should be possible with C# and C++. I would guess that they are using one of those to write this? – chrismead Jul 01 '12 at 19:36