Reputation: 1009
I am trying to scrape a ListView like control from an external application. Now I am using System.Windows.Automation
. With AutoIt v3 I've extracted the following information about the exact control I want to scrape text from:
>>>> 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
Now, I have noticed ID = 1510520
and by using it I would be able to get the control
AutomationElement element = AutomationElement.FromHandle(1510520);
The control looks like a ListView or similar to that, but I can't do anything else with it.
How I can now get the content of this control?
UPDATE:
Thanks to Jimi's recommendation inspect.exe from Windows 10 SDK worked the best! I was able to drill down to DataGridView.
Upvotes: 1
Views: 1684
Reputation: 32248
I assume you can find the Window that contains the DataGridView from which to extract the data. The GeDataGridViewDataTable()
method expects the Handle of that Window.
Let's break down these methods:
To get the AutomationElement of a Window of interest when its handle is already known, we could just use window =
AutomationElement.FromHandle([Window Handle])
.
▶ Here I'm using an AndCodition because you may have ProcessID and Window Title, so instead of filtering using AutomationElement.ControlTypeProperty
and AutomationElement.NativeWindowHandleProperty
, you could use the AutomationElement.ProcessIdProperty
and AutomationElement.NameProperty
as the conditions.
If the Window is found, the first child elements in the TreeScope.SubTree
scope (all the UI elements in that Window) are parsed to find the first element of type Table (ControlType.Table).
▶ Of course that Window might host more than one DataGridView: in this case, we can use FindAll()
instead of FindFirst()
, then determine which is which using some other condition (the number of Columns, the text of Headers, content of Cells, location, size, parent container etc.).
When the DataGridView of interest is found, we can extract the content of its Cells.
Here comes the second method, GetDataGridViewRowsCollection()
:
Top Row
. We can then use the header text to name the Columns of the DataTable that will store the data extracted. Otherwise, just add some default names.ControlType.Header
, if any.param
array argument of DataTable.Rows.Add()
.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;
}
Upvotes: 2