שתף באמצעות


Select ListviewItem using LVM_SETITEMSTATE-Solved

Question

Saturday, December 7, 2013 6:59 AM

Below is my code where I am trying to send LVM_SETITEMSTATE message to a Listview Control. But there is no resonse in the Target Application. I tried to investigate the issue with Spy++ where I could see the  variable values  of LV_ITEM struct received by the Application is Zero. But in my code I am actually sending  values  in the LV_ITEM struct which are not getting passed as zeros to the target application.  I am not sure why this happens.Could anyone Please help me with this issue?

Actual Code:

'Declarations and Struct Definition

Public Const LVM_FIRST = &H1000
Public Const LVM_SETITEMSTATE = (LVM_FIRST + 43)
Public Const LVM_GETITEMSTATE As Long = (LVM_FIRST + 44)
Public Const LVM_GETSELECTEDCOUNT = (LVM_FIRST + 50)

Public Const LVIS_SELECTED = &H2
Public Const LVN_ITEMACTIVATE As Long = (LVM_FIRST + 14)
Public Const NM_DBLCLK As Long = (LVM_FIRST + 3)

Public Const LVIF_STATE = &H8
Public Const LVIS_STATEIMAGEMASK As Long = &HF000

Public Type LV_ITEM
   mask As Long
   iItem As Long
   iSubItem As Long
   state As Long
   stateMask As Long
   pszText As String
   cchTextMax As Long
   iImage As Long
   lParam As Long
   iIndent As Long
End Type

'Actual program sending the LVM_ SETITEMSTATE Message

Sub test3()
 Dim LV As LV_ITEM

** 'I tried this buffer with 40b ytes size also which is the total size of my LV_ITEM struct but still not working.**

'I am assigning this buffer to the pszText in the LV_ITEM struct.

 szString = Space(0)    
   With LV
      .mask = LVIF_STATE
      .iItem = 2
      .iSubItem = 0
      .state = 0
      .cchTextMax = 0
      .pszText = szString
      .stateMask = LVIS_SELECTED
   End With

'2952362 is the hwnd for the Listview control and I am trying to  access the second item

Call SendMessage(2952364, LVM_SETITEMSTATE, 2, LV)                    

End Sub

All replies (5)

Monday, December 9, 2013 7:14 PM ✅Answered

Thanks Razerz. I am now able to select the listview item by following your code logic.

Below is the VBA version of your logic .Thanks for the solution. This would help a lot of people as only limited info is available on this in web. Nothing like the sample working code as given in this thread.

VBA Code to do LVM_SETITEMSTATE:

Private Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, ByVal lpBaseAddress As Long, lpBuffer As Any, ByVal nSize As Byte, ByVal lpNumberOfBytesWritten As Long) As Long
Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function VirtualAllocEx Lib "kernel32" (ByVal hProcess As Long, ByVal lpAddress As Long, ByVal dwSize As Byte, ByVal flAllocationType As Long, ByVal flProtect As Long) As Long
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

Private Type LUID
 UsedPart As Long
 IgnoredForNowHigh42BitPart As Long
End Type
 
Public Const LVM_FIRST = &H1000
Public Const LVM_SETITEMSTATE = (LVM_FIRST + 43)
Public Const LVM_GETITEMSTATE As Long = (LVM_FIRST + 44)
Public Const LVM_GETSELECTEDCOUNT = (LVM_FIRST + 50)

Public Const LVIS_SELECTED = &H2
Public Const LVN_ITEMACTIVATE As Long = (LVM_FIRST + 14)
Public Const NM_DBLCLK As Long = (LVM_FIRST + 3)

Public Const LVIF_STATE = &H8
Public Const LVIS_STATEIMAGEMASK As Long = &HF000
Public Const VMWRITE = &H20
Public Const PAGE_READWRITE = &H4
Public Const PROCESS_ALL_ACCESS = &H1F0FFF
Public Const PROCESS_VM_WRITE = &H20
Public Const PROCESS_VM_OPERATION = &H8

Public Type LV_ITEM
   mask As Long
   iItem As Long
   iSubItem As Long
   state As Long
   stateMask As Long
   pszText As String
   cchTextMax As Long
   iImage As Long
   lParam As Long
   iIndent As Long
End Type

Private Const MEM_COMMIT = &H1000

Sub select_listitem()
 Dim LV As LV_ITEM
 
   Dim numSelected As Long
   Dim t As Long
   Dim lhwndProcess As Long
   Dim lExitCode As Long
   Dim lRetVal As Long
   Dim lhThisProc As Long
   Dim lhTokenHandle As Long
   Dim tLuid As LUID
   Dim tTokenPriv As TOKEN_PRIVILEGES, tTokenPrivNew As TOKEN_PRIVILEGES
   Dim lBufferNeeded As Long
   Dim lpid as Long  

   Const PROCESS_ALL_ACCESS = &H1F0FFF, PROCESS_TERMINATE = &H1
   Const ANYSIZE_ARRAY = 1, TOKEN_ADJUST_PRIVILEGES = &H20
   Const TOKEN_QUERY = &H8, SE_DEBUG_NAME As String = "SeDebugPrivilege"
   Const SE_PRIVILEGE_ENABLED = &H2
  'hWnd = 2100726
 
  szString = Space(0)

   With LV
      .mask = LVIF_STATE
      '.iItem = 2
      '.iSubItem = 0
      .state = &HF
      '.cchTextMax = 0
      '.pszText = szString
      .stateMask = LVIS_SELECTED
   End With

hwnd= XXXX'put the window handle of the listviewcontrol here
  
k = Len(LV)

lRetVal = GetWindowThreadProcessId(hWnd, lpid)

lhwndProcess = OpenProcess(PROCESS_ALL_ACCESS, False, lpid)
vpoint = VirtualAllocEx(lhwndProcess, 0, 40, MEM_COMMIT, PAGE_READWRITE)
result = WriteProcessMemory(lhwndProcess, vpoint, LV, Len(LV), 0)

 
  Call SendMessage(hWnd, LVM_SETITEMSTATE, 2, vpoint) 'The second item in the list view is selected

End Sub


Saturday, December 7, 2013 2:44 PM

Hi,

 The first thing you need to do is get the VB.NET signatures for the functions, structures, and constants. I listed some of them below. Also i see you are using (2952364) as the handle of the listview. The listview handle will change every time you open and close the window that has the listview on it so you will need to use FindWindow or FindWindowEx to get the Parent window handle and then use FindWindowEx to search threw the child windows of it to get the handle of the listview each time this is executed.

 Now the bad news is that it is not as easy as just getting the listview handle and sending a message to it to select an item. It requires getting the processes ID, allocating unmanaged memory, and reading and writing to the processes memory.

 I have tried this in the past and was never fully successful at doing this. There is a lot of similar questions on the net but, there is not a working example anywhere that i have found yet. Perhaps there is a specific reason that you need to select an item in the listview that may be able to be done a different way such as, if you just want to open an explorer window with a certain folder/file selected or something similar?

Imports System.Runtime.InteropServices

Public Class Form1
    Public Const LVM_FIRST As Integer = &H1000
    Public Const LVM_SETITEMSTATE As Integer = (LVM_FIRST + 43)
    Public Const LVM_GETITEMSTATE As Integer = (LVM_FIRST + 44)
    Public Const LVM_GETSELECTEDCOUNT As Integer = (LVM_FIRST + 50)
    Public Const LVN_ITEMACTIVATE As Integer = (LVM_FIRST + 14)
    Public Const NM_DBLCLK As Integer = (LVM_FIRST + 3)
    Public Const LVIS_SELECTED As Integer = &H2
    Public Const LVIF_STATE As Integer = &H8
    Public Const LVIS_STATEIMAGEMASK As Integer = &HF000

    <StructLayout(LayoutKind.Sequential)> _
    Public Structure LVITEM
        Public mask As Integer
        Public iItem As Integer
        Public iSubItem As Integer
        Public state As Integer
        Public stateMask As Integer
        <MarshalAs(UnmanagedType.LPTStr)> Public pszText As String
        Public cchTextMax As Integer
        Public iImage As Integer
        Public lParam As Integer
        Public iIndent As Integer
        Public iGroupId As Integer
        Public cColumns As Integer
        Public puColumns As Integer
        Public piColFmt As Integer
        Public iGroup As Integer
    End Structure

    <DllImport("user32.dll", EntryPoint:="SendMessageW")> _
    Private Shared Function SendMessageW(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As Integer, ByVal lParam As LVITEM) As Integer
    End Function

    <DllImport("user32.dll", EntryPoint:="SendMessageW")> _
    Private Shared Function SendMessageW(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As Integer, ByVal lParam As Integer) As Integer
    End Function

End Class

Sunday, December 8, 2013 9:48 AM

Hello IronRazers,

Thanks for your reply.I would like to add more details on what I am trying to do.

I am trying to automate the Listview Control in a Tool called IBM ISA using legacy VBA Macros. I am not using VB.NET through Visual studio. Even if you can give me a working code for this in VB.NET , it is fine. I will be able to convert it accordingly.

As you said the window handle is expected to change but I am capturing it successfully using UIAutomation feature.But UIAutomation does not seem to be working properly with some controls especially when executing the control methods. Hence  I am using Win32 API concepts in my automation script to invoke controls. Could you please help me on how to pass the LV_ITEM struct properly to the Target Application. I need some guidelines on the memory management part.

Previously I was able to invoke menu control items using heapalloc feature in the current process and then creating the struct on it, but the same concept is not working in the case of Listviewitem. In the menu controls I actually sent the struct to get menuitem info. But here in Listview item I am actually passing the struct data to set the values in the Target Application. Any Thoughts on how to accomplish LVM_SETITEMSTATE??


Monday, December 9, 2013 1:03 AM

Hi,

 I finally figured it out so here is an example that shows how to select or un-select a specific item in an external program`s SysListView32 by its index. Don`t be shocked if you tell it to select item 1 (the 2nd item) and it appears to select the wrong item because, it depends on how the external program arranges the items in the listview.

Imports System.Runtime.InteropServices
Imports System.Text

Public Class Form1
    Private Const LVM_FIRST As UInteger = &H1000
    Private Const LVM_GETITEMCOUNT As UInteger = (LVM_FIRST + 4)
    Private Const LVM_SETITEM As UInteger = (LVM_FIRST + 6)
    Private Const LVM_SETITEMSTATE As UInteger = (LVM_FIRST + 43)
    Private Const LVIS_SELECTED As UInteger = &H2
    Private Const LVIF_STATE As UInteger = &H8


    Private Const MEM_COMMIT As UInteger = &H1000
    Private Const MEM_RELEASE As UInteger = &H8000
    Private Const PAGE_READWRITE As UInteger = &H4

    Private Const PROCESS_VM_READ As UInteger = &H10
    Private Const PROCESS_VM_WRITE As UInteger = &H20
    Private Const PROCESS_VM_OPERATION As UInteger = &H8

    <DllImport("kernel32.dll", SetLastError:=True)> Private Shared Function CloseHandle(ByVal handle As IntPtr) As Boolean
    End Function
    <DllImport("kernel32.dll", SetLastError:=True)> Private Shared Function OpenProcess(ByVal dwDesiredAccess As UInteger, ByVal bInheritHandle As Boolean, ByVal dwProcessId As Integer) As IntPtr
    End Function
    <DllImport("user32.dll", SetLastError:=True)> Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal message As UInteger, ByVal wParam As Integer, ByVal lParam As IntPtr) As Integer
    End Function
    <DllImport("kernel32.dll", SetLastError:=True)> Private Shared Function VirtualAllocEx(ByVal hProcess As IntPtr, ByVal lpAddress As IntPtr, ByVal dwSize As Integer, ByVal flAllocationType As UInteger, ByVal flProtect As UInteger) As IntPtr
    End Function
    <DllImport("kernel32.dll", SetLastError:=True)> Private Shared Function VirtualFreeEx(ByVal hProcess As IntPtr, ByVal lpAddress As IntPtr, ByVal dwSize As Integer, ByVal dwFreeType As UInteger) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function
    <DllImport("kernel32.dll", SetLastError:=True)> Private Shared Function WriteProcessMemory(ByVal hProcess As IntPtr, ByVal lpBaseAddress As IntPtr, ByRef lpBuffer As LV_ITEM, ByVal nSize As Integer, ByRef lpNumberOfBytesWritten As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function
    <DllImport("user32.dll", SetLastError:=True)> Private Shared Function GetWindowThreadProcessId(<InAttribute()> ByVal hWnd As IntPtr, ByRef lpdwProcessId As Integer) As Integer
    End Function
    <DllImport("user32.dll", SetLastError:=True)> Private Shared Function FindWindowExW(ByVal hwndParent As IntPtr, ByVal hwndChildAfter As IntPtr, <InAttribute(), MarshalAs(UnmanagedType.LPTStr)> ByVal lpszClass As String, <InAttribute(), MarshalAs(UnmanagedType.LPTStr)> ByVal lpszWindow As String) As IntPtr
    End Function

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
    Public Structure LV_ITEM
        Public mask As UInteger
        Public iItem As Integer
        Public iSubItem As Integer
        Public state As UInteger
        Public stateMask As UInteger
        Public pszText As IntPtr
        Public cchTextMax As Integer
        Public iImage As Integer
        Public lParam As IntPtr
        Public iIndent As Integer
        Public iGroupId As Integer
        Public cColumns As Integer
        Public puColumns As IntPtr
        Public piColFmt As IntPtr
        Public iGroup As Integer
    End Structure


    Private Sub Button_SelectItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button_SelectItem.Click
        SelectItem(1, True) 'Sets the 2nd item in the listview to the Selected state if 2 items exist in the listview
    End Sub

    Private Sub Button_UnSelectItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button_UnSelectItem.Click
        SelectItem(1, False) 'Sets the 2nd item in the listview to the Un-Selected state if 2 items exist in the listview
    End Sub

    Private Sub SelectItem(ByVal iIndex As Integer, ByVal selState As Boolean)

        Dim hLstVw As IntPtr = GetListViewHandle() 'Get the listview`s handle

        If Not hLstVw.Equals(IntPtr.Zero) Then 'Check if the listview handle was found

            Dim itemCount As Integer = SendMessage(hLstVw, LVM_GETITEMCOUNT, 0, IntPtr.Zero)
            If itemCount < iIndex Then Exit Sub 'If the item index to be set is higher than the listview`s item count then exit the sub now

            Dim procId As Integer = Nothing
            GetWindowThreadProcessId(hLstVw, procId) 'Get the listview`s process Id

            'Open the process id and set access rights to read and write to the process`s memory
            Dim hProc As IntPtr = OpenProcess(PROCESS_VM_OPERATION Or PROCESS_VM_READ Or PROCESS_VM_WRITE, False, procId)

            Dim lvi As New LV_ITEM 'Create a new LV_ITEM structure set to select or unselect the item depending on the selState parameter
            lvi.mask = LVIF_STATE
            lvi.stateMask = LVIS_SELECTED
            If selState Then
                lvi.state = &HF
            Else
                lvi.state = 0
            End If

            'Reserve space in the processes memory and write the new LV_ITEM structure`s data to it
            Dim LvItemPtr As IntPtr = VirtualAllocEx(hProc, IntPtr.Zero, Marshal.SizeOf(lvi), MEM_COMMIT, PAGE_READWRITE)
            WriteProcessMemory(hProc, LvItemPtr, lvi, Marshal.SizeOf(lvi), 0)

            SendMessage(hLstVw, LVM_SETITEMSTATE, iIndex, LvItemPtr) 'Send the message to set the items state

            VirtualFreeEx(hProc, LvItemPtr, 0, MEM_RELEASE) 'Release the processes memory used for the LV_ITEM structure`s data
            CloseHandle(hProc)
        Else
            MessageBox.Show("ListView handle not found.")
        End If
    End Sub

    'This is the function i used to get the SysListView32 handle of an explorer window
    Private Function GetListViewHandle() As IntPtr
        Dim hParent As IntPtr = FindWindowExW(IntPtr.Zero, IntPtr.Zero, "CabinetWClass", Nothing)
        If Not hParent.Equals(IntPtr.Zero) Then
            Dim hChld_1 As IntPtr = FindWindowExW(hParent, IntPtr.Zero, "SHELLDLL_DefView", Nothing)
            If Not hChld_1.Equals(IntPtr.Zero) Then
                Dim hChld_2 As IntPtr = FindWindowExW(hChld_1, IntPtr.Zero, "DUIViewWndClassName", Nothing)
                If Not hChld_2.Equals(IntPtr.Zero) Then
                    Dim hChld_3 As IntPtr = FindWindowExW(hChld_2, IntPtr.Zero, "DirectUIHWND", Nothing)
                    If Not hChld_3.Equals(IntPtr.Zero) Then
                        Dim hChld_4 As IntPtr = FindWindowExW(hChld_3, IntPtr.Zero, "CtrlNotifySink", Nothing)
                        If Not hChld_4.Equals(IntPtr.Zero) Then
                            Dim hChld_5 As IntPtr = FindWindowExW(hChld_4, IntPtr.Zero, "SysListView32", "FolderView")
                            If Not hChld_5.Equals(IntPtr.Zero) Then
                                Return hChld_5 'Return the handle to the SysListView32
                            End If
                        End If
                    End If
                End If
            End If
        End If
        Return IntPtr.Zero
    End Function
End Class

Monday, December 9, 2013 9:29 PM

Hi,

 Glad to here it helped.   :)

 No there is not many examples of interacting with an external listview on the net and i bet i tried most if not all of them but, it seemed there was one problem or another with all of them.