Jalaj

December 13, 2006

Adding File Names to a ListBox - 2

Filed under: Visual Basic, WinAPI — Jalaj @ 6:12 am

Continued from Adding File Names to a ListBox

Now that we aim at populating the listbox by opening the file(s) already associated with our app, we need to know the way to do it.

When a file is opened by double-clicking on it, the associated application opens up and the name of the file is passed to it through variable “Command$” (”Command” may also be used, that’s a matter of preference). This can be passed to the AddToList procedure to populate the list.

AddToList Command$

This is OK when we need to open a single file, but when more than one file is opened at a time each file opens up a new instance of the app, populating each listbox with a single file, which is surely not what we aim at. Thankfully when an app is opened it can determine if another instance of the app is already running or not by accessing App.PrevInstance property which returns ‘true’ or ‘false’ depending on whether app is already opened or not.

If App.PrevInstance Then
    '' Commands to Pass the command$ to previous instance
    End
End If

This will close the duplicate instances after passing the Command$ to the first one. But the question is how to pass Command$ to previous instance. While the parent instance can access the listbox directly, this will not be easy for other instances. We need to make API calls to various functions defined in User32.dll

Let’s make changes to our project, so that only one instance of the “Form” gets loaded.

In the Project Explorer, right-click on the project click on “Project Properties…”. Change Startup Object under “General tab” from “Form1″ to “Sub Main” and then press “OK”.

Add a module in the project, this will be the place where we would be adding all our codes now.

Public Const APPNAME = "My New App"
Public blnCloseThisInstance As Boolean

Sub Main()

    If Not App.PrevInstance Then
        Load Form1
        Form1.Visible = True
        Form1.Caption = APPNAME
        blnCloseThisInstance = False
    Else
        blnCloseThisInstance = True
    End If

    If blnCloseThisInstance Then
	'' Commands to Pass the command$ to listbox in loaded instance
        End
    End If

End Sub

The above code will ensure that only first instance of the app opens up the form, and the opened form will have caption defined in Constant APPNAME. The duplicate instances will not open forms and end up after passing the filename (the value in Command$).

Now onwards all the instances including the first one will access the Form named as in contant APPNAME using API calls.

Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, _
	ByVal lpWindowName As String) As Long

Public lngAppHandle As Long

' Below line would come somewhere in Sub Main()
lngAppHandle = FindWindow(vbNullString, APPNAME)

FindWindow function in the above code will help us to find handle of the Window (the only instance of the form opened). In order to add filename to the listbox we would also require to know the handle of the Listbox for which We would use FindWindowEx function which returns handles for Child Windows/controls.

Public Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, _
	ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long

Public lngListBoxHandle As Long

' Below line would come somewhere in Sub Main()
lngListBoxHandle = FindWindowEx(lngAppHandle, 0, "ThunderRT6ListBox", "")

“ThunderRT6ListBox” above is the classname for the listbox and varies for different versions of VB. I used Spy++, the utility that comes with Visual Studio, to determine the classname, details of which I may explain sometimes later.

Now that we have the handle for listbox we will instruct the listbox to add filename by SendMessage function. We would make these call from AddToList procedure which we will make public so that the form can also access the same procedure (The AddToList procedure define earlier in the form can be safely deleted)

Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, _
	ByVal wMsg As Long, ByVal wParam As Integer, ByVal lParam As Any) As Long

Public Const LB_ADDSTRING = &H180
Public Const LB_FINDSTRING = &H18F

Public varJunkVar As Variant
Public lngIndexToItem As Variant

Public Function AddToList(ByVal txtFileName As String)

    lngIndexToItem = SendMessage(lngListBoxHandle, LB_FINDSTRING, -1, ByVal CStr(txtFileName))
    If lngIndexToItem = -1 Then
        varJunkVar = SendMessage(lngListBoxHandle, LB_ADDSTRING, 0, ByVal CStr(txtFileName))
    End If

End Function

See the difference SendMessage made to the AddToList procedure. While earlier we had to iterate through all items to see if a filename already existed, this time LB_FINDSTRING message sent to listbox returns the index if string is found in the list otherwise returns -1

Now lets put all things togather and paste to the newly created module. The AddToList procedure from the form can be safely deleted

Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, _
	ByVal lpWindowName As String) As Long

Public Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, _
	ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long

Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, _
	ByVal wMsg As Long, ByVal wParam As Integer, ByVal lParam As Any) As Long

Public Const APPNAME = "My New App"
Public Const LB_ADDSTRING = &H180
Public Const LB_FINDSTRING = &H18F

Public lngAppHandle As Long
Public lngListBoxHandle As Long
Public blnCloseThisInstance As Boolean

Public varJunkVar As Variant
Public lngIndexToItem As Long

Sub Main()
    If Not App.PrevInstance Then
        Load Form1
        Form1.Visible = True
        Form1.Caption = APPNAME
        blnCloseThisInstance = False
        lngAppHandle = FindWindow(vbNullString, APPNAME)
    Else
        blnCloseThisInstance = True
        Do While lngAppHandle = 0
            lngAppHandle = FindWindow(vbNullString, APPNAME)
        Loop
    End If

    lngListBoxHandle = FindWindowEx(lngAppHandle, 0, "ThunderRT6ListBox", "")

    If lngListBoxHandle = 0 Then
        lngListBoxHandle = FindWindowEx(lngAppHandle, 0, "ThunderListBox", "")
    End If

    If Trim(Command$) <> "" Then
        AddToList Trim(Mid(Command$, 2, Len(Command$) - 2))
    End If

    If blnCloseThisInstance Then
        End
    End If

End Sub

Public Function AddToList(ByVal txtFileName As String)

    lngIndexToItem = SendMessage(lngListBoxHandle, LB_FINDSTRING, -1, ByVal CStr(txtFileName))
    If lngIndexToItem = -1 Then
        varJunkVar = SendMessage(lngListBoxHandle, LB_ADDSTRING, 0, ByVal CStr(txtFileName))
    End If

End Function

Let’s review the code for parts that are not discussed yet.

While a single statement is used finding lngAppHandle for the first instance of app, a do while loop has been used in case of duplicate instances. This is done here as when more than one files are opened the first instance may take time to load the form. The duplicate instances will not get the handles until the form is fully loaded. This loop though has resulted in performance degradation when first as well as duplicate instances are created at the same time, helps us achive our aim. There is an alternate way which I may take up later.

An additional call for finding handle for Listbox is added with classname as “ThunderListBox”. This is called when Class “ThunderRT6ListBox” returns no handle. This is used to make sure that app also works when executed directly from VB development Environment, where the ListBox created is of “ThunderListBox” class (determined from Spy++).

value from Command$ is added after removing leading and trailing double quotes.

The API function calls FindWindow, FindWindowEx and SendMessage required elaboration which I may do sometime later. Same with Spy++ utility

Thus ends our search for adding filenames to the listbox on click of filenames. Except for the performance degradation when first instance is not already running, the approach is acceptable.

2 Comments »

  1. [...] Continued from Adding File Names to a ListBox - 2 [...]

    Pingback by Adding File Names to a ListBox - 3 « Jalaj — December 15, 2006 @ 6:50 am

  2. [...] the copy/paste that many do). This post spanned three posts Adding File Names to a ListBox, Adding File Names to a ListBox - 2, Adding File Names to a ListBox - [...]

    Pingback by The Blog Revisited - 1 « Jalaj — November 23, 2007 @ 12:07 pm

RSS feed for comments on this post. TrackBack URI

Leave a comment

Blog at WordPress.com.