Home > Net >  Finding _and then clicking_ ListBox Items from another program built in VB6 (ThunderRT6ListBox)
Finding _and then clicking_ ListBox Items from another program built in VB6 (ThunderRT6ListBox)

Time:02-10

wracking my brain on this one.

I have successfully utilized this link to successfully grab text of the items of a ListBox (ThunderRT6ListBox specifically) working with WinAPI functions. What I want to accomplish is to select and then double-click the necessary item from this list. I've successfully been able to find the WindowRect of the ListBox itself, but I cannot get the cursor position relative to this Box and desired item.

I have to submit a double-click on this as the author of the application I'm ScreenScraping set the double-click event of the item to open a child window where parameters are set relative to the selected item.

I'm only going to post the part where I'm having trouble - again - I've successfully found the data and have been able to get the WindowRect of the ListBox - I'm now trying to click and double-click the item needed. I imagine I need to then set my mouse cursor position using another winAPI, but when attempting to get the WindowRect of the Item it fails and gives a 0 for both x,y coordinates. I have also tried SendKeys and other variants of this with an attempt to "Select" the item by value. . . this also fails.

I am writing in VB.NET but do not mind answers posted with C#, as there are converters and these answers are more prevalent for everything anyway (which is also helping me to learn that language too).

 Public Function GetListBoxContents(ByVal listBoxHwnd As IntPtr) As List(Of String)
    Dim cnt As Integer = CInt(SendMessage(listBoxHwnd, LB_GETCOUNT, IntPtr.Zero, Nothing))

    Dim listBoxContent As List(Of String) = New List(Of String)()

    For i As Integer = 0 To cnt - 1
      Dim sb As StringBuilder = New StringBuilder(256)
      Dim getTextLen As Integer = CInt(SendMessage(listBoxHwnd, LB_GETTEXTLEN, Nothing, Nothing))
      Dim getText As IntPtr = SendMessage(listBoxHwnd, LB_GETTEXT, CType(i, IntPtr), sb)
      Dim ComRect As RECT
      GetWindowRect(listBoxHwnd, ComRect)
      Debug.Print("Rect's x coordinate = " & ComRect.x) 'Produces valid X coord when looking
                                                         at listboxHwnd - if I look at "getText"
                                                         it fails - I assume because this really
                                                         is not 
      Debug.Print("Rect's y coordinate = " & ComRect.y)
      Dim lparam As Integer = MakeLong(ComRect.x, ComRect.y)
      SetForegroundWindow(listBoxHwnd)
      If sb.ToString Like "*COM5*" Then
        Debug.Print("hWnd of ListBox item = ", getText)
        WindowsEnumerator.SendMessage(getText, WM_LBUTTONDOWN, &H1, lparam)
        WindowsEnumerator.SendMessage(getText, WM_LBUTTONUP, &H1, lparam)

        PostMessage(getText, WM_LBUTTONDBLCLK, &H1, lparam)

        PostMessage(getText, BM_CLICK, &H1, lparam)

        SendInputs.Mouse_Click()

        SendInputs.SendKey(Convert.ToChar(13))

        SendKeys.SendWait("{ENTER}")

      End If
      listBoxContent.Add(sb.ToString)
    Next

    Return listBoxContent
  End Function

CodePudding user response:

After losing some of my hair, I finally managed to figure out how to do what I need.

HUGE kudos to Unhnd_Exception on the Daniweb site for this neat, intuitive class. (I'm linking to the post, but also posting the content of Unhnd_Exception's class here in case that site ever goes down. If I have not attributed it enough to the user, please help me make a better approach to this without losing the code - this was a life-saver! (REF#1))

Also - I think ANYONE who dabbles in the API Realm who has struggled to find the ACTUAL values of the Constants will really appreciate this following link. It saved my bacon on the remainder of the code. (REF#2) Additionally, having the MSDN Page for List Box Messages was the other Key I needed as it contained the different methods to communicate with these objects! (REF#3)

Final Methodology used to accomplish my task:

  Public Const LB_GETCOUNT As Integer = &H18B ' Gets the count of items in ListBox
  Public Const LB_GETTEXTLEN As Integer = &H18A ' Gets Length of Text for given
                                                  Item based on hWnd
  Public Const LB_GETTEXT As Integer = &H189 ' Gets Text
  Public Const LB_GETITEMRECT As Integer = &H198 ' Gets ListBox Item RECT - NOTE:
                                                 ' This still failed for me and returned
                                                 ' "0" for all dimensions in this case!
  Public Const LB_SELECTSTRING As Integer = &H18C ' Hoped this was useful - not so much
  Public Const LB_SETCURSEL As Integer = &H186 ' THIS ONE - NICE Beauty!
              'LB_SETCURSEL will focus the selection on the object by index
              'It will also scroll the box if there are enough items.
  Public Const LB_SETSEL As Integer = &H185 ' Ultimately did not use this one.


  Public Function GetListBoxContents(ByVal listBoxHwnd As IntPtr) As List(Of String)
    Dim cnt As Integer = CInt(SendMessage(listBoxHwnd, LB_GETCOUNT, IntPtr.Zero, Nothing))

    Dim listBoxContent As List(Of String) = New List(Of String)()

    For i As Integer = 0 To cnt - 1
      Dim sb As StringBuilder = New StringBuilder(256)
      Dim itmRect As RECT
      Dim getTextLen As Integer = CInt(SendMessage(listBoxHwnd, LB_GETTEXTLEN, Nothing, Nothing))
      Dim getText As IntPtr = SendMessage(listBoxHwnd, LB_GETTEXT, CType(i, IntPtr), sb)

      SendMessage(getText, LB_SETCURSEL, CType(i, IntPtr), Nothing)
      Dim ComRect As RECT
      GetWindowRect(listBoxHwnd, ComRect)
      Debug.Print("Rect's x coordinate = " & ComRect.x)
      Debug.Print("Rect's y coordinate = " & ComRect.y)
      Debug.Print("Rect's h coordinate = " & ComRect.h)
      Debug.Print("Rect's w coordinate = " & ComRect.w)
      SendMessageRect(getText, LB_GETITEMRECT, CType(i, IntPtr), itmRect)
      Debug.Print("itmRect's x coordinate = " & itmRect.x) 'All of these returned 0's
      Debug.Print("itmRect's y coordinate = " & itmRect.y) 'Perhaps I'm calling the routine
      Debug.Print("itmRect's h coordinate = " & itmRect.h) 'incorrectly???  Would love to know
      Debug.Print("itmRect's w coordinate = " & itmRect.w) 'why this fails.
      SetCursorPos(ComRect.x   10, ComRect.h - 10) 'THIS ONE.  Moves my cursor in position
                                                   'to select the appropriate object.
                                                   'If I could make the GETITEMRECT
      SetForegroundWindow(listBoxHwnd)             'Method to work, this would be MOST ideal
                                                   'As it would always be concurrent
                                                   'with the given item and be less
      If sb.ToString Like "*COM5*" Then            'prone to failure. Moves selection to bottom.
        Debug.Print("hWnd of ListBox item = ", getText)
        SendMessage(listBoxHwnd, LB_SETCURSEL, CType(i, IntPtr), Nothing)
        SendInputs.Double_Click() ' Here is the clicker - worked like a champ

      End If
      listBoxContent.Add(sb.ToString)
    Next
    Return listBoxContent
  End Function

Sendinput that actually works (REF#1)

Value of the constants for the Windows 32-bit API (REF#2)

List Box Messages (REF#3)

Code used from Unhnd_Exception on Daniweb in entirety:

Imports System.Runtime.InteropServices

Public Class SendInputs

    Private Const KeyDown As Integer = &H0
    Private Const KeyUp As Integer = &H2

    <DllImport("user32.dll")> _
  Private Shared Function SendInput( _
        ByVal nInputs As Integer, _
        ByVal pInputs() As INPUT, _
        ByVal cbSize As Integer) As Integer
    End Function

    <StructLayout(LayoutKind.Explicit)> _
   Private Structure INPUT
        'Field offset 32 bit machine 4
        '64 bit machine 8
        <FieldOffset(0)> _
        Public type As Integer
        <FieldOffset(8)> _
        Public mi As MOUSEINPUT
        <FieldOffset(8)> _
        Public ki As KEYBDINPUT
        <FieldOffset(8)> _
        Public hi As HARDWAREINPUT
    End Structure

    Private Structure MOUSEINPUT
        Public dx As Integer
        Public dy As Integer
        Public mouseData As Integer
        Public dwFlags As Integer
        Public time As Integer
        Public dwExtraInfo As IntPtr
    End Structure

    Private Structure KEYBDINPUT
        Public wVk As Short
        Public wScan As Short
        Public dwFlags As Integer
        Public time As Integer
        Public dwExtraInfo As IntPtr
    End Structure

    Private Structure HARDWAREINPUT
        Public uMsg As Integer
        Public wParamL As Short
        Public wParamH As Short
    End Structure

      Public Shared Sub SendKey(ByVal key As Char)
        Dim Inpts(1) As INPUT

        'key down
        Inpts(0).type = 1
        Inpts(0).ki.wVk = Convert.ToInt16(CChar(key))
        Inpts(0).ki.dwFlags = KeyDown

        'key up
        Inpts(1).type = 1
        Inpts(1).ki.wVk = Convert.ToInt16(CChar(key))
        Inpts(1).ki.dwFlags = KeyUp

        SendInput(2, Inpts, Marshal.SizeOf(GetType(INPUT)))
    End Sub
End Class

I modified his Button click Subroutine (full content shown below) to accomplish what I need (removed the binding to a button click within the program). If you duplicate the final line SendInput(2, Inpts, Marshal.SizeOf(GetType(INPUT))) you essentially get 2 clicks in rapid succession. In my implementation I added a "Mouse_Click" routine (as mentioned - no WinForm Control needed), and I also implemented a "Double_Click" routine which gives the result I need.)

Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
        'raises the key down and up events

        Dim Inpts(1) As INPUT

        'key down
        Inpts(0).type = 1
        Inpts(0).ki.wVk = Convert.ToInt16(CChar("J"))
        Inpts(0).ki.dwFlags = 0

        'key up
        Inpts(1).type = 1
        Inpts(1).ki.wVk = Convert.ToInt16(CChar("J"))
        Inpts(1).ki.dwFlags = 2

        SendInput(2, Inpts, Marshal.SizeOf(GetType(INPUT)))
    End Sub
  •  Tags:  
  • Related