Search code examples
vb.netlistviewui-automationinspectorautomationelement

UI Automation - Retrieving data from a SysListView32 (or any Listview)


I'm trying to retrieve data from a SysListView32 object with the code below but it's returning an empty string.

The elements that I need to retrieve are those highlighted in red, as well as the others contained in the other ControlType.ListItem elements, according to the Inspector's print.

Can some one check what's wrong with my code?

Inspector

Msgbox("Position the mouse cursor on the screen and press ENTER.")

Dim pt As POINTAPI
GetCursorPos(pt)

Dim hwnd As IntPtr = WindowFromPoint(pt)

Dim hwnd As IntPtr = 67202
Dim el As AutomationElement = AutomationElement.FromHandle(hwnd)
Dim walker As TreeWalker = TreeWalker.ContentViewWalker
Dim i As Integer = 0
Dim child As AutomationElement = walker.GetFirstChild(el)

While child IsNot Nothing
    '
    Dim k As Integer = 0
    Dim child2 As AutomationElement = walker.GetFirstChild(child)

    While child2 IsNot Nothing
        Console.WriteLine(child2.Current.ToString)
        child2 = walker.GetNextSibling(child2)
    End While

    child = walker.GetNextSibling(child)
End While

Solution

  • The SysListView32 may not provide the information requested if its current view state is not LV_VIEW_DETAILS, so we should temporarily (if the current view state is different), use the MultipleViewPattern of its AutomationElement to verify the view state and change it if necessary using the MultipleViewPattern.SetCurrentView() method.

    The SetCurrentView() method uses the same values of the Win32 Control.

    Then use the AutomationElement FindAll() method of the to find all child elements of type ControlType.DataItem or ControlType.ListItem (using an OrCondition).

    For each of them, get all child items of type ControlType.Edit and ControlType.Text (using another OrCondition).

    The position of each Item in the list is retrieved using the Item's GridItemPattern, to access the Item's Row property.

    Finally, we restore the previous View State if we had to change it.


    The code in the example fills a Dictionary(Of Integer, ListViewItem) (named sysListViewItems here), containing all the Items extracted from the SysListView32.

    • The Integer Key represents the position of an Item in the original ListView
    • The ListViewItem is a .Net object generated from the array of values (as strings) extracted from each item.

    If you don't need ListViewItem objects, you can just store the List(Of String), represented by the itemsText object, instead of creating a ListViewItem here:

    sysListViewItems.Add(gridPattern.Current.Row, New ListViewItem(itemsText.ToArray())).  
    

    The Handle of a SysListView32 can also be acquired enumerating the children of its Top Level Window by ClassName.
    AutomationElement.RootElement provides the current Desktop Element:

    Dim parentWindow = AutomationElement.RootElement.FindFirst(
        TreeScope.Children, New PropertyCondition(AutomationElement.NameProperty, "[Window Caption]"))
    Dim sysListView32 = parentWindow.FindAll(
        TreeScope.Subtree, New PropertyCondition(AutomationElement.ClassNameProperty, "SysListView32"))
    

    If more than one SysListView32 is found, filter by Header content, direct parent ControlType or ClassName or anything else that allows to single it out.

    UI Automation requires a reference to the UIAutomationClient and UIAutomationTypes assemblies.


    Imports System.Windows.Automation
    
    ' Find the ListView Handle as described or the ListView UI Element directly
    Dim sysListViewHandle = [GetSysListView32Handle()]
    
    Dim sysListViewElement = AutomationElement.FromHandle(sysListViewHandle)
    If sysListViewElement Is Nothing Then Return
    
    Dim sysListViewItems = New Dictionary(Of Integer, ListViewItem)()
    Dim mulView As MultipleViewPattern = Nothing
    Dim pattern As Object = Nothing
    Dim currentView As Integer = -1
    
    If sysListViewElement.TryGetCurrentPattern(MultipleViewPattern.Pattern, pattern) Then
        mulView = DirectCast(pattern, MultipleViewPattern)
        currentView = mulView.Current.CurrentView
        If currentView <> ListViewWState.LV_VIEW_DETAILS Then
            mulView.SetCurrentView(ListViewWState.LV_VIEW_DETAILS)
        End If
    End If
    
    Dim childItemsCondition = New OrCondition(
        New PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.DataItem),
        New PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ListItem))
    
    Dim childItems = sysListViewElement.FindAll(TreeScope.Children, childItemsCondition)
    If childItems.Count = 0 Then Return
    
    Dim subItemsCondition = New OrCondition(
        New PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit),
        New PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Text))
    
    For Each item As AutomationElement In childItems
        Dim itemsText = New List(Of String)()
        Dim subItems = item.FindAll(TreeScope.Children, subItemsCondition)
        For Each subItem As AutomationElement In subItems
            itemsText.Add(subItem.Current.Name)
        Next
        Dim gridPattern = DirectCast(subItems(0).GetCurrentPattern(GridItemPattern.Pattern), GridItemPattern)
        sysListViewItems.Add(gridPattern.Current.Row, New ListViewItem(itemsText.ToArray()))
    Next
    
    If mulView IsNot Nothing Then
        mulView.SetCurrentView(currentView)
    End If
    
    Friend Enum ListViewWState
        LV_VIEW_ICON = &H0
        LV_VIEW_DETAILS = &H1
        LV_VIEW_SMALLICON = &H2
        LV_VIEW_LIST = &H3
        LV_VIEW_TILE = &H4
    End Enum