C# 如何使用UI自动化从ListView或类似控件中获取文本?

C# 如何使用UI自动化从ListView或类似控件中获取文本?,c#,vb.net,ui-automation,C#,Vb.net,Ui Automation,我试图从外部应用程序中获取类似ListView的控件。现在我使用的是System.Windows.Automation。使用AutoIt v3,我提取了以下有关要从中提取文本的确切控件的信息: >>>> Control <<<< Class: WindowsForms10.Window.8.app.0.34f5582_r6_ad1 Instance: 20 ClassnameNN: WindowsForms10.Window.8.app

我试图从外部应用程序中获取类似ListView的控件。现在我使用的是
System.Windows.Automation
。使用AutoIt v3,我提取了以下有关要从中提取文本的确切控件的信息:

>>>> Control <<<<
Class:  WindowsForms10.Window.8.app.0.34f5582_r6_ad1
Instance:   20
ClassnameNN:    WindowsForms10.Window.8.app.0.34f5582_r6_ad120
Name:   
Advanced (Class):   [CLASS:WindowsForms10.Window.8.app.0.34f5582_r6_ad1; INSTANCE:20]
ID: 1510520
Text:   
Position:   182, 164
Size:   1411, 639
ControlClick Coords:    300, 202
Style:  0x56010000
ExStyle:    0x00000000
Handle: 0x0000000000170C78
该控件看起来像ListView或类似的控件,但我无法对其执行任何其他操作

现在如何获取此控件的内容

更新:
由于Jimi的推荐,Windows 10 SDK中的inspect.exe工作得最好!我能够深入到DataGridView


我假设您可以找到包含要从中提取数据的DataGridView的窗口。
GeDataGridViewDataTable()
方法需要该窗口的句柄

我们来分解这些方法:

要在已知句柄时获取感兴趣窗口的AutomationElement,我们可以使用
Window=

► 这里我使用的是一个,因为您可能有ProcessID和窗口标题,所以不必使用
AutomationElement.ControlTypeProperty
AutomationElement.NativeWindowHandleProperty
进行筛选,您可以使用
AutomationElement.ProcessIdProperty
AutomationElement.NameProperty
作为条件

如果找到该窗口,将解析
树作用域.SubTree
作用域中的第一个子元素(该窗口中的所有UI元素)以查找Table()类型的第一个元素

► 当然,该窗口可能承载多个DataGridView:在这种情况下,我们可以使用
FindAll()
而不是
FindFirst()
,然后使用其他条件(列数、标题文本、单元格内容、位置、大小、父容器等)确定哪个是哪个

找到感兴趣的DataGridView后,我们可以提取其单元格的内容。
下面是第二个方法,
GetDataGridViewRowsCollection()

  • 第一个过滤掉DataGridView滚动条,可能还有网格的其他子控件(定制可能包括一些)
  • 之后,我们检查DGV是否有标题:如果有,则第一个子行元素名称为
    顶行
    。然后,我们可以使用标题文本来命名将存储提取数据的DataTable的列。否则,只需添加一些默认名称
  • 然后,对于每个行元素,我们枚举其子元素,表示单元格。我添加了一个过滤器以排除行标题单元格,
    ControlType.Header
    ,如果有
  • 然后,我们迭代单元格的集合,使用方法提取它们的值,将属性类型设置为,将这些值添加到一个列表中,该列表将提供
    DataTable.Rows.add()的
    参数
    数组参数

专用数据表GeDataGridViewDataTable(IntPtr windowHwnd)
{
var条件=新的AND条件(
新属性条件(AutomationElement.ControlTypeProperty、ControlType.Window),
新属性条件(AutomationElement.NativeWindowHandleProperty,windowHwnd.ToInt32())
);
var window=AutomationElement.RootElement.FindFirst(TreeScope.Children,条件);
if(window==null)返回null;
var dgv=window.FindFirst(TreeScope.Subtree,
新属性条件(AutomationElement.ControlTypeProperty、ControlType.Table);
如果(dgv==null)返回null;
var dt=GetDataGridViewRowsCollection(dgv);
返回dt;
}
私有数据表GetDataGridViewRowsCollection(AutomationElement dgv)
{
var dt=新数据表();
//跳过滚动条和其他子元素
var条件=新的或条件(
新属性条件(AutomationElement.ControlTypeProperty、ControlType.Custom),
新属性条件(AutomationElement.ControlTypeProperty、ControlType.Header)
);
var rows=dgv.FindAll(TreeScope.Children,condition).OfType().ToList();
bool hasColumnHeader=(行[0]。Current.Name=“顶行”);
//第一个元素是标题(如果有)
var dgvHeaderColumns=行[0]。FindAll(TreeScope.Children,Condition.TrueCondition);
//跳过顶部/左侧标题
对于(int i=1;i
这是一个DataGridView。这些行具有自动化角色或行(
0x1C
)。但这些只是容器。您必须获取每行的子元素并检查它们的ControlType属性(第一个元素可能具有
标题
角色,其他的
编辑
复选框
等),您可能会发现使用应用程序对确定公开的自动化模式很有用。如果subject控件是DatagridView,它将公开@TnTinMn,谢谢,但是Windows的可访问性洞察没有显示有关应用程序的任何内容。还有其他类似的应用程序可以做同样的事情吗?@Jimi你有什么代码可以让我看看吗?我想我可以。您知道如何获取拥有DataGridView的窗口的句柄吗?我的意思是,在编程上,不使用工具。您使用什么详细信息来查找它(标题、流程ID、特定内容等)。无论如何,我会发布一些标准的UIAutomation(
System.Windows.Automation
)给我
AutomationElement element = AutomationElement.FromHandle(1510520);
private DataTable GeDataGridViewDataTable(IntPtr windowHwnd)
{
    var condition = new AndCondition(
        new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window),
        new PropertyCondition(AutomationElement.NativeWindowHandleProperty, windowHwnd.ToInt32())
    );
    var window = AutomationElement.RootElement.FindFirst(TreeScope.Children, condition);
    if (window == null) return null;
    var dgv = window.FindFirst(TreeScope.Subtree, 
        new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Table));

    if (dgv == null) return null;
    var dt = GetDataGridViewRowsCollection(dgv);
    return dt;
}

private DataTable GetDataGridViewRowsCollection(AutomationElement dgv)
{
    var dt = new DataTable();

    // Skips ScrollBars and other child elements
    var condition = new OrCondition(
        new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom),
        new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Header)
    );

    var rows = dgv.FindAll(TreeScope.Children, condition).OfType<AutomationElement>().ToList();
    bool hasColumnHeader = (rows[0].Current.Name == "Top Row");

    // First element is the Header (if there's one)
    var dgvHeaderColumns = rows[0].FindAll(TreeScope.Children, Condition.TrueCondition);
        
    // Skip the Top/Left header
    for (int i = 1; i < dgvHeaderColumns.Count; i++) {
        dt.Columns.Add(hasColumnHeader ? dgvHeaderColumns[i].Current.Name : "Column"+i);
    }

    // Skips the Row Header, if any
    var notCondition = new NotCondition(new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Header));
    foreach (AutomationElement row in rows) {
        var cells = row.FindAll(TreeScope.Children, notCondition);
        var values = new List<object>();
        foreach (AutomationElement cell in cells) {
            values.Add(cell.GetCurrentPropertyValue(ValuePattern.ValueProperty));
        }
        dt.Rows.Add(values.ToArray());
    }
    return dt;
}