UI Automation not working for DataGridView

☆樱花仙子☆ 提交于 2019-12-05 19:57:55

The code that you are copying is flawed. I just tested this scenario with an adaptation of this sample program factoring in your code above, and it works.

The key difference is that the code above is using TreeScope.Children to grab the datagrid element. This option only grabs immediate children of the parent, so if your datagrid is nested it's not going to work. Change it to use TreeScope.Descendants, and it should work as expected.

var datagrid = bw.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, "dgvControlProperties"));

Here is a link to how the various Treescope options behave. Also, I don't know how your binding the rows to the grid, but I did it like this in my test scenario and it worked flawlessly.

Hopefully this helps.

public class DataObject
{
    public string FieldA { get; set; }
    public string FieldB { get; set; }
    public string FieldC { get; set; }
}

List<DataObject> items = new List<DataObject>();
items.Add(new DataObject() {FieldA="foobar",FieldB="foobar",FieldC="foobar"});
items.Add(new DataObject() { FieldA = "foobar", FieldB = "foobar", FieldC = "foobar" });
items.Add(new DataObject() { FieldA = "foobar", FieldB = "foobar", FieldC = "foobar" });

dg.ItemsSource = items;

Your code looks fine though this could be a focus issue.

Even though you are getting a reference to these automation element objects you should set focus on them (using the aptly named SetFocus method) before using them.

Try:

var desktop = AutomationElement.RootElement;

desktop.SetFocus();

// Find AutomationElement for the App's window
var bw = desktop.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ProcessIdProperty, (int)processID));

and if that does not work try explicitly focusing on the dataGrid prior to calling "FindAll" on it i.e.

datagrid.SetFocus()

Why does the DataGridView return 0 rows?

The DataGridViewRows have a ControlType of ControlType.Custom. So I modified the line

// Find all rows from the DataGridView
var loginLines = datagrid.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.DataItem));

To

var loginLines = datagrid.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom));

That gets you all the rows within the DataGridView. But your code has that within the loop.

What pattern is supported by DataGridView (not DataGrid)?

LegacyIAccessiblePattern. Try this-

 LegacyIAccessiblePattern legacyPattern = null;

       try
       {
           legacyPattern = datagrid.GetCurrentPattern(LegacyIAccessiblePattern.Pattern) as LegacyIAccessiblePattern;
       }
       catch (InvalidOperationException ex)
       {
           // It passes!
       }

As I commented on @James answer, there is no support for UIA for DataGridView (again, not DataGrid).

If you search in google with terms: "UI Automation DataGridView" the first result has an incomplete answer. Mike replies with what provider class (Obviously one that extends DataGridView)to create within the source, unfortunately I have no clue as to how to make UIA load that class a provider. If anyone can shed some clues, Phil and I will be extremely pleased!

EDIT

Your DataGridView should implement the interfaces in that link above. Once you do that the ControlType of Row will be ControlType.DataItem instead of ControlType.Custom. You can then use it how you'd use a DataGrid.

EDIT

This is what I ended up doing -

Creating a Custom DataGridView like below. Your datagridview could subclass this too. That will make it support ValuePattern and SelectionItemPattern.

public class CommonDataGridView : System.Windows.Forms.DataGridView,
    IRawElementProviderFragmentRoot,
    IGridProvider,
    ISelectionProvider
{.. }

The complete code can be found @ this msdn link.

Once I did that, I played a bit with visualUIVerify source. Here is how I can access the gridview's cell and change the value. Also, make note of the commented code. I didn't need that. It allows you to iterate through rows and cells.

private void _automationElementTree_SelectedNodeChanged(object sender, EventArgs e)
    {
        //selected currentTestTypeRootNode has been changed so notify change to AutomationTests Control
        AutomationElementTreeNode selectedNode = _automationElementTree.SelectedNode;
        AutomationElement selectedElement = null;

        if (selectedNode != null)
        {
            selectedElement = selectedNode.AutomationElement;

            if (selectedElement.Current.ClassName.Equals("AutomatedDataGrid.CommonDataGridViewCell"))
            {
                if(selectedElement.Current.Name.Equals("Tej")) //Current Value
                {

                    var valuePattern = selectedElement.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;

                    valuePattern.SetValue("Jet");
                }



                //Useful ways to get patterns and values

                //System.Windows.Automation.SelectionItemPattern pattern = selectedElement.GetCurrentPattern(System.Windows.Automation.SelectionItemPattern.Pattern) as System.Windows.Automation.SelectionItemPattern;


                //var row = pattern.Current.SelectionContainer.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ClassNameProperty, "AutomatedDataGrid.CommonDataGridViewRow", PropertyConditionFlags.IgnoreCase));

                //var cells = row.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.ClassNameProperty, "AutomatedDataGrid.CommonDataGridViewCell", PropertyConditionFlags.IgnoreCase));
                //foreach (AutomationElement cell in cells)
                //{
                //    Console.WriteLine("**** Printing Cell Value **** " + cell.Current.Name);

                //    if(cell.Current.Name.Equals("Tej")) //current name
                //    {

                //        var valuePattern = cell.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;

                //        valuePattern.SetValue("Suraj");
                //    }
                //}

                //Select Row
                //pattern.Select();

                //Get All Rows
                //var arrayOfRows = pattern.Current.SelectionContainer.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.ClassNameProperty, "AutomatedDataGrid.CommonDataGridViewRow", PropertyConditionFlags.IgnoreCase));


                //get cells
                //foreach (AutomationElement row in arrayOfRows)
                //{
                //   var cell = row.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ClassNameProperty, "AutomatedDataGrid.CommonDataGridViewCell", PropertyConditionFlags.IgnoreCase));


                //    var gridItemPattern = cell.GetCurrentPattern(GridItemPattern.Pattern) as GridItemPattern;

                //    // Row number.
                //    Console.WriteLine("**** Printing Row Number **** " + gridItemPattern.Current.Row);

                //    //Cell Automation ID
                //    Console.WriteLine("**** Printing Cell AutomationID **** " + cell.Current.AutomationId);

                //    //Cell Class Name
                //    Console.WriteLine("**** Printing Cell ClassName **** " + cell.Current.ClassName);

                //    //Cell Name
                //    Console.WriteLine("**** Printing Cell AutomationID **** " + cell.Current.Name);                        
                //}

            }
        }

        _automationTests.SelectedElement = selectedElement;
        _automationElementPropertyGrid.AutomationElement = selectedElement;
    }

Hopefully this helps.

Khang Tran

I run into this problem too, after researching, i figured out that you can only access data grid view with LegacyIAccessible. However, .NET does not support this. So, here are the steps:

  1. there are 2 versions of UIA: managed version and unmanaged version (native code). In some cases, the unmanaged version can do what the managed version can't.

=> as said here, using tblimp.exe (go along with Windows SDK) to generate a COM wrapper around the unmanaged UIA API, so we can call from C#.
I have done it here

  1. we can use this to access DataGridView now but with very limited data, you can use Inspect.exe to see.

The code is:

using InteropUIA = interop.UIAutomationCore;

if (senderElement.Current.ControlType.Equals(ControlType.Custom))
{
    var automation = new InteropUIA.CUIAutomation();
    var element = automation.GetFocusedElement();
    var pattern = (InteropUIA.IUIAutomationLegacyIAccessiblePattern)element.GetCurrentPattern(10018);
    Logger.Info(string.Format("{0}: {1} - Selected", pattern.CurrentName, pattern.CurrentValue));
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!