C# UI自动化不适用于DataGridView

C# UI自动化不适用于DataGridView,c#,winforms,datagridview,microsoft-ui-automation,C#,Winforms,Datagridview,Microsoft Ui Automation,在尝试了几种解决方案后,我急需帮助 我尝试了几种方法,最后复制了,但仍然坚持使用来自的解决方案 让我们来谈谈代码,请考虑下面的注释: // Get Process ID for desired window handle uint processID = 0; GetWindowThreadProcessId(hwnd, out processID); var desktop = AutomationElement.RootElement; // Find AutomationElement

在尝试了几种解决方案后,我急需帮助

我尝试了几种方法,最后复制了,但仍然坚持使用来自的解决方案

让我们来谈谈代码,请考虑下面的注释:

// Get Process ID for desired window handle
uint processID = 0;
GetWindowThreadProcessId(hwnd, out processID);

var desktop = AutomationElement.RootElement;

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

// Find the DataGridView in question
var datagrid = bw.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, "dgvControlProperties"));

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

// Badumm Tzzz: loginLines has 0 items, foreach is therefore not executed once 

foreach (AutomationElement loginLine in loginLines)
{
    var loginLinesDetails = loginLine.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom));

    for (var i = 0; i < loginLinesDetails.Count; i++)
    {
        var cacheRequest = new CacheRequest 
        { 
            AutomationElementMode = AutomationElementMode.None,
            TreeFilter = Automation.RawViewCondition
        };

        cacheRequest.Add(AutomationElement.NameProperty);
        cacheRequest.Add(AutomationElement.AutomationIdProperty);

        cacheRequest.Push();

        var targetText = loginLinesDetails[i].FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ClassNameProperty, "TextBlock"));

        cacheRequest.Pop();

        var myString = targetText.Cached.Name;
    }

}
行是预先添加到
DataGridView
的,如下所示:

dgvControlProperties.Rows.Add(new object[] { false, "Some Text", "Some other text" });
我正在编译到.NETFramework 4.5。尝试了UI自动化客户端的常规用户权限和提升的管理员权限,两者都产生了此处描述的相同结果

为什么
DataGridView
返回0行

为什么我不能得到其中一个模式

谢谢你帮了我


更新: 詹姆斯·赫普没有帮我玩这个把戏。以下代码返回所有行(包括标题):


然后可以通过
ControlType的
ControlType
来识别标题单元格。标题

您的代码看起来不错,尽管这可能是一个焦点问题

即使您获得了对这些自动化元素对象的引用,您也应该在使用它们之前设置它们的焦点(使用恰当命名的方法)

尝试:

如果这不起作用,请尝试在调用“FindAll”之前明确关注dataGrid,即

datagrid.SetFocus()

您复制的代码有缺陷。我刚刚在上面的代码中对分解进行了修改,测试了这个场景,效果很好

关键区别在于上面的代码使用
TreeScope.Children
来获取datagrid元素。此选项仅获取父级的直接子级,因此如果您的datagrid是嵌套的,它将无法工作。将其更改为使用
TreeScope.substands
,它应该可以正常工作

var datagrid = bw.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, "dgvControlProperties"));
查看各种Treescope选项的行为。另外,我不知道如何将行绑定到网格,但我在测试场景中就是这样做的,它工作得非常完美

希望这能有所帮助

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;
公共类数据对象
{
公共字符串字段a{get;set;}
公共字符串字段b{get;set;}
公共字符串字段c{get;set;}
}
列表项=新列表();
添加(新数据对象(){FieldA=“foobar”,FieldB=“foobar”,FieldC=“foobar”});
添加(新数据对象(){FieldA=“foobar”,FieldB=“foobar”,FieldC=“foobar”});
添加(新数据对象(){FieldA=“foobar”,FieldB=“foobar”,FieldC=“foobar”});
dg.ItemsSource=项目;

为什么DataGridView返回0行

DataGridViewRows的ControlType为ControlType.Custom。所以我修改了线路

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

这将获取DataGridView中的所有行。但是你的代码已经在循环中了

DataGridView(不是DataGrid)支持什么模式

LegacyAccessiblePattern。试试这个-

 LegacyIAccessiblePattern legacyPattern = null;

       try
       {
           legacyPattern = datagrid.GetCurrentPattern(LegacyIAccessiblePattern.Pattern) as LegacyIAccessiblePattern;
       }
       catch (InvalidOperationException ex)
       {
           // It passes!
       }
正如我在@jamesanswer上所评论的那样,对于DataGridView(同样,不是DataGrid)没有对UIA的支持

如果你在谷歌中搜索“UI自动化DataGridView”,答案不完整。Mike回答说要在源代码中创建什么提供程序类(显然是扩展DataGridView的类),不幸的是,我不知道如何使UIA加载该类作为提供程序。如果有人能提供一些线索,菲尔和我会非常高兴的

编辑

DataGridView应该实现上面链接中的接口。执行此操作后,行的ControlType将是ControlType.DataItem,而不是ControlType.Custom。然后可以像使用数据网格一样使用它

编辑

这就是我最后做的-

创建自定义DataGridView,如下所示。您的datagridview也可以将其子类化。这将使它支持ValuePattern和SelectionItemPattern

public class CommonDataGridView : System.Windows.Forms.DataGridView,
    IRawElementProviderFragmentRoot,
    IGridProvider,
    ISelectionProvider
{.. }
完整的代码可以在@中找到

一旦我做到了这一点,我玩了一点VisualUniversifSource。下面是如何访问gridview的单元格并更改值。另外,记下注释的代码。我不需要那个。它允许您遍历行和单元格

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;
    }

希望这有帮助。

我也遇到了这个问题,经过研究,我发现您只能使用
LegacyIAccessible
访问数据网格视图。但是,.NET不支持此操作。因此,以下是步骤:

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;
  • UIA有两个版本:托管版本和非托管版本(本机代码)。在某些情况下,非托管版本可以执行托管版本无法执行的操作
  • =>如本文所述,使用
    tblimp.exe
    (与Windows SDK一起使用)围绕非托管UIA API生成COM包装,因此我们可以从C#调用。
    我已经做到了

  • 我们现在可以使用它来访问DataGridView,但由于数据非常有限,您可以使用
    Inspect.exe
    查看 代码是:

    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));
    }
    


    您得到了什么错误?对象登录行包含0个项,尽管DataGridView中有一些项与前面提到的dgv.Rows.Add(…)方法调用一起添加。因此这里没有异常。实际上,我的程序只是没有明显的原因(对我来说)不提取行。在Catch块中,您写道:“它失败了。”
    tablePattern=datagrid.GetCurrentPattern(tablePattern.Pattern)作为tablePattern
    没有引发任何错误?能否在其方法和窗体事件处理程序方法的上下文中显示数据绑定代码?当尝试在桌面上设置焦点时,我收到一个InvalidOperationException-显然无法聚焦桌面。聚焦DataGridView不会改变任何内容。在这种情况下,DataGridView是表单的直接子级,无论如何,感谢您的链接!在将行直接添加到DGV和代码之间切换不会改变错误行为:(因为你的回答是最详细的,所以你得到了声誉。很抱歉,我只提供了部分详细信息,我看不到你的控制层次结构或数据绑定逻辑。你的答案正常吗?是的,我发布了一行内容,作为问题的更新。LegacyI还有其他选择吗
    public class CommonDataGridView : System.Windows.Forms.DataGridView,
        IRawElementProviderFragmentRoot,
        IGridProvider,
        ISelectionProvider
    {.. }
    
    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;
        }
    
    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));
    }