ListView Demo (IE Favorites 2)

in Visual Basic®

by Rick Meyer        Home

To see the Favorites 1 Demo click TreeView Control.

For a simpler ListView click ListView Demo.

This is the second in the series on Internet Explorer Favorites. The first demonstrated how to load all the favorite folders in a TreeView. This demo takes the next step and displays the Favorites in a ListView Control.

Although shown here as a demo, the compiled project can actually be useful. The reason for developing this program was to rapidly place URL's in the Clipboard. You will find if you click on a Tree Node or a ListView ListItem you will see that the item text is placed in the Textbox. The text is also copied to the clipboard.

This is valuable as a speedy way to get your Favorite URL's. If after you compile it, you drag the EXE icon from Windows Explorer to the Desktop or QuickLaunch ToolBar you will have rather fast way of placing a URL in the ClipBoard.

If you watch when you first start the program, it takes a while before your favorite folder Tree and ListView favorites are displayed. This is because a recursive directory search is made to find every folder and every favorite.URL file must be opened and read until the URL is found.

Since this takes such a long time, this program is designed to automatically save all this information in a separate .ini file in the App.Path. Subsequently when you load this program the information will appear almost instantly from the .ini file.

If you have updated your favorites in IExplorer you can force a new search by clicking "Refresh" which will automatically overwrite the .ini file with the new search info.

Now you know one of the reasons it takes a while for IExplorer to load before you can run it.

"%20" vs. " "

You may have noticed that some URL's you have seen have the %20 packed in them instead of spaces. This protects against the URL string being delimited (ended) at the space.

To accomodate this quirk, the %20 has been saved to the .ini file. Although the %20 in not displayed in the textbox if you click on an URL, it is placed in the ClipBoard.

      Instructions for Building this Project:

  1. Start a new standard exe
  2. On Form1 put a CommandButton named Command1
  3. On Form1 put a TextBox named Text1
  4. On Form1 put a TreeView Control named TreeView1
  5. Set the TreeView1.LabelEdit property to Manual
  6. Set the TreeView1.HideSelection property to False
  7. On Form1 put a ListView Control named ListView1
  8. Set the ListView1.View property to 3-lvwReport
  9. Set the ListView1.LabelEdit property to Manual
10. Set the ListView1.FullRowSelect property to True

There is no need to position or size the above controls
on the form since that is all done in the Form_Load.

To find the TreeView Control click on the VB Menu->Project->Components and select Microsoft Windows Common Controls 6. After you click OK the TreeView Control (and some others) will appear in the toolbar usually on the left of the VB IDE (Integrated Development Environment).
Now you are ready for the code. Select all of the following code (by clicking on the word 'Option' three times) and copy it to the clipboard [Ctrl][Insert]. Then paste it into the code window of Form1 with [Shift][Insert]. Variable Key
% As Integer
& As Long
! As Single
# As Double
$ As String
Option Explicit

'===========================================================
' Declarations for the file searching operations
'===========================================================
Const FILE_ATTRIBUTE_DIRECTORY = &H10

Private Type FILETIME
  dwLowDateTime     As Long
  dwHighDateTime    As Long
End Type

Private Type WIN32_FIND_DATA
  dwFileAttributes  As Long
  ftCreationTime    As FILETIME
  ftLastAccessTime  As FILETIME
  ftLastWriteTime   As FILETIME
  nFileSizeHigh     As Long
  nFileSizeLow      As Long
  dwReserved0       As Long
  dwReserved1       As Long
  cFileName         As String * 260
  cAlternate        As String * 14
End Type

Private Declare Function FindFirstFile _
    Lib "kernel32" Alias "FindFirstFileA" ( _
    ByVal lpFileName As String, _
    lpFindFileData As WIN32_FIND_DATA) As Long
   
Private Declare Function FindNextFile _
    Lib "kernel32" Alias "FindNextFileA" ( _
    ByVal hFindFile As Long, _
    lpFindFileData As WIN32_FIND_DATA) As Long

Private Declare Function FindClose Lib "kernel32" _
    (ByVal hFindFile As Long) As Long

'===========================================================
' Declarations for Clicking ListView SubItems
'===========================================================
Private Type lvwMsgInfo
    x As Long
    y As Long
    Flgs As Long
    Itm As Long
    SubItm As Long
End Type

Private Declare Function SendMessage _
    Lib "user32" Alias "SendMessageA" ( _
        ByVal hwnd As Long, _
        ByVal lMsg As Long, _
        ByVal wParam As Long, _
        lParam As Any) As Long

Dim lvwMsg As lvwMsgInfo

'===========================================================
' Declarations to Store the Favorites in Memory
'===========================================================
Const DIMINCR = 32

Private Type favType
    Key As String
    Name As String
    URL As String
End Type

Dim fav() As favType
Dim favCnt%, dimCntr%, iniFile$

'===========================================================
' Procedures for Finding Favorite Folders and URL files
'===========================================================
'The Recursive Procedure to find all favorite folders
' and favorite URL's (files)
Private Sub GetFavorites(ByVal oldPath$, ByVal newFolder$, _
                            Optional start As Boolean)
    Dim fFirst&, fNext&, found$, curPath$
    Dim w As WIN32_FIND_DATA
    
    curPath = AddSlash(oldPath) & newFolder
    
    'Add the folder as a node
    If start Then
        'Needed for the first node only
        TreeView1.Nodes.Add , , curPath, newFolder
    Else
        'Subsequent nodes
        TreeView1.Nodes.Add oldPath, tvwChild, _
                            curPath, newFolder
    End If
    
    DoEvents
    'Now search the folder
    fFirst = FindFirstFile(AddSlash(curPath) & "*", w)
    If fFirst = 0 Then Exit Sub 'Empty folder
    
    Do
        found = TrimNull(w.cFileName)
        If w.dwFileAttributes And _
          FILE_ATTRIBUTE_DIRECTORY Then
            If found <> "." And found <> ".." Then
                'A folder is found so call this Sub
                ' recursively to add the node
                GetFavorites curPath, found
            End If
        Else
            If Len(found) > 4 Then
                If LCase$(Right$(found, 4)) = ".url" Then
                    'A favorite (file) is found
                    found = Left$(found, Len(found) - 4)
                    AddFavorite curPath, found
                End If
            End If
        End If
            
        DoEvents
        fNext = FindNextFile(fFirst, w)
    Loop While fNext
        
    fNext = FindClose(fFirst)
End Sub

Private Sub AddFavorite(favKey$, favName$, Optional favURL$)
    favCnt = favCnt + 1
                        
    If favCnt >= dimCntr Then
        dimCntr = dimCntr + DIMINCR
        ReDim Preserve fav(dimCntr)
    End If
    
    fav(favCnt).Key = favKey
    fav(favCnt).Name = favName
    fav(favCnt).URL = favURL
    
    If favURL = "" Then
        'The URL is not supplied so
        '  open the file and get it
        fav(favCnt).URL = getURL(AddSlash(favKey) & favName)
    End If
End Sub

'===========================================================
' The typical contents of a favorite file follows:
' (stored in the C:\Windows\Favorites directory)
'===========================================================
' [Default]
' BASEURL=http://www.yahoo.com/
'
' [InternetShortcut]
' URL=http://www.yahoo.com/
' Modified=807340260326C10144
'===========================================================
Private Function getURL(fPath$)
    Dim j%, f%, s$, t$
    Dim gotHeader As Boolean
    
    'First open the URL file
    f = FreeFile
    Open fPath & ".url" For Input As f
    
    Do Until EOF(f)
        Line Input #f, s
        s = MyTrim(s)
        t = LCase$(s)
        
        If InStr(t, "netshortcut") Then gotHeader = True
        
        'Line Input until the [InternetShortcut] section
        If gotHeader Then
            'See if the specifier is in the string
            j = InStr(t, "url=")
            If j Then
                'If it is then get the portion
                '  after the specifier
                j = Len(s) - j
                If j > 3 Then
                    s = Right$(s, j - 3)
                Else
                    s = ""
                End If
                Exit Do
            End If
        End If
    Loop
    
    Close f
    getURL = s
End Function

'===========================================================
'                  Clicking Events
'===========================================================
Private Sub Command1_Click()
    Static working
    
    'Do not allow click while loading
    If working Then Exit Sub
    working = True
    
    'Initialize memory storage of favorites
    favCnt = 0
    dimCntr = 0
    Erase fav
    TreeView1.Nodes.Clear
    
    'Do the work
    GetFavorites "C:\Windows\", "Favorites", True
    ShowMyFolder
    SaveINIFavorites
    
    working = False
End Sub

' Interestingly ListView does not have a built in way
' to tell if a subitem has been clicked. This sub does it.
'
' Also, if you click the first column slightly off the text
' (to the right) no click event happens unless you use set
' the ListView.FullRowSelect = True
Private Sub ListView1_MouseDown(Button As Integer, _
        Shift As Integer, x As Single, y As Single)

    lvwMsg.x = x / Screen.TwipsPerPixelX
    lvwMsg.y = y / Screen.TwipsPerPixelY
    
    SendMessage ListView1.hwnd, 4153, 0, lvwMsg
    
    If lvwMsg.Flgs And 14 Then
        ShowLabel lvwMsg.Itm + 1, lvwMsg.SubItm
    End If
End Sub

' Inconveniently:
' There is no .SubItem(0) allowed so there is always extra
' code for the first Item that is not a SubItem.
'
' Additionally:
' A key field is assigned to the ListItem so that the
' fav().URL could be retrieved (remember the "%20" in the
' string is shown as a space). To put the data with the %20
' into the ClipBoard (mentioned in the opening comments),
' ListItems(i).Key holds the pointer to the fav() array for
' the item displayed.
'
' Interestingly, the Key is a string and will not accept a
' number, so an "L" is concatenated in front of the
' CStr(number) when adding (see Sub ShowInListView) -
' thus requiring the Right$ function here.
Private Sub ShowLabel(ByVal i%, ByVal s%)
    Clipboard.Clear
    With ListView1.ListItems(i)
      Select Case s
        Case 0
            Text1 = .Text
            Dim j%
            j = CInt(Right$(.Key, Len(.Key) - 1))
            Clipboard.SetText fav(j).URL
        Case 1
            Text1 = .SubItems(s)
            Clipboard.SetText Text1
      End Select
    End With
End Sub

'Clicking on a TreeView item
Private Sub TreeView1_NodeClick( _
        ByVal Node As MSComctlLib.Node)
        
    'Populate the ListView
    ShowInListView Node.Key
    
    'Places TreeView item in TextBox & ClipBoard
    Text1 = Node.Key
    Clipboard.SetText Text1
End Sub

'Populate the ListView from the fav() array
Private Sub ShowInListView(ky$)
    Dim j%, l As ListItem
    
    With ListView1
        .ListItems.Clear
        
        'Run through the whole fav() array and look for
        ' the saved string that matches this node's .Key
        For j = 1 To favCnt
            If fav(j).Key = ky Then
                'Show the URL
                Set l = .ListItems.Add(, "L" & CStr(j), _
                    Replace(fav(j).URL, "%20", " "))
                
                'Show the Name (fav name = file name)
                l.SubItems(1) = fav(j).Name
            End If
        Next
    End With
End Sub

'===========================================================
'        General String Handling Procedures
'===========================================================
Private Function AddSlash$(ByVal s$)
    If Len(s) Then
        If Right$(s, 1) <> "\" Then s = s & "\"
    End If
    
    AddSlash = s
End Function

Private Function TrimNull$(ByVal s$)
    Dim pos As Integer
    
    pos = InStr(s, Chr$(0))
    If pos Then s = Left$(s, pos - 1)
    
    TrimNull = s
End Function

'The intrinsic Trim$ Function does NOT strip all
' Chars <= Asc(32) - so I wrote my own
Private Function MyTrim$(ByVal s$)
    Dim j&, k&, l&
    
    l = Len(s)
    For j = 1 To l
        If Asc(Mid$(s, j, 1)) > 32 Then Exit For
    Next
    For k = l To 1 Step -1
        If Asc(Mid$(s, k, 1)) > 32 Then Exit For
    Next
    
    s = IIf(k > j, Mid$(s, j, k - j + 1), "")
    MyTrim = s
End Function

'===========================================================
' Procedures to Save and Load the Nodes and ListItems
'===========================================================
Private Sub LoadINIFavorites()
    Dim j%, f%, nd%
    Dim favName$, favKey$, favURL$
    Dim ndKey$, ndParent$, ndText$
        
    f = FreeFile
    Open iniFile For Input As f
    
    'First get the number of nodes
    Input #f, nd
    
    'Next get the first node - special case
    '  because it does not have a parent
    Input #f, ndParent, ndKey, ndText
    TreeView1.Nodes.Add , , ndKey, ndText
    
    'Get the rest of the nodes
    For j = 2 To nd
        Input #f, ndParent, ndKey, ndText
        TreeView1.Nodes.Add ndParent, tvwChild, ndKey, ndText
    Next
    
    'Get the favorites
    Do Until EOF(f)
        Input #f, favKey, favName, favURL
        AddFavorite favKey, favName, favURL
    Loop
    Close f
End Sub

Private Sub SaveINIFavorites()
    Dim j%, f%, favName$, favKey$, favURL$, tmp$
        
    f = FreeFile
    Open iniFile For Output As f
    
    'First save the number of nodes so we know
    ' how many to retrieve in LoadINIFavorites
    Write #f, TreeView1.Nodes.Count
    
    'Save the nodes (favorite folders)
    For j = 1 To TreeView1.Nodes.Count
        If j = 1 Then
            'The first node does not have a parent
            tmp = "first"
        Else
            tmp = TreeView1.Nodes(j).Parent.Key
        End If
        
        Write #f, tmp, TreeView1.Nodes(j).Key, _
                        TreeView1.Nodes(j).Text
    Next
    
    'Save the favorites
    For j = 1 To favCnt
        Write #f, fav(j).Key, fav(j).Name, fav(j).URL
    Next
    
    Close f
End Sub

'===========================================================
'                  Form Initialization
'===========================================================
Private Sub Form_Load()
    'Size and place the controls
    Move 1000, 1000, 9400, 4800
    Caption = "Internet Explorer Favorites"
    TreeView1.Move 120, 120, 2300, 3400
    ListView1.Move 2480, 120, 6700, 3400
    Command1.Move 500, 3700, 1200, 375
    Command1.Caption = "Refresh"
    Text1.Move 2480, 3700, 6700, 375
    Text1 = ""
    
    'Add the ListView Columns
    With ListView1.ColumnHeaders
        .Add , , "Favorite URL", 3500
        .Add , , "Favorite Name", 3100
    End With
    
    iniFile = AddSlash(App.Path) & App.EXEName & ".ini"
    
    'Check if the .ini file exists
    If Dir$(iniFile) = "" Then
        'No - do the recursive search
        Command1_Click
    Else
        'Yes - load from the .ini file
        LoadINIFavorites
        ShowMyFolder
    End If
End Sub

'Expand the TreeView for the favorite folder specified
'  and populate the ListView for that folder's favorites
Private Sub ShowMyFolder()
    Dim j%, mf$
    
    'Note: You can specify the folder as
    '       a command line parameter
    mf = Command$
    
    If mf = "" Then
'***********************************************************
'  Edit here for the Folder you want Automatically Opened
'     perhaps like: mf = "C:\Windows\Favorites\Channels"
'
        mf = "C:\Windows\Favorites"
'***********************************************************
    End If
    
    mf = LCase$(mf)
    'Search for the node specified
    For j = 1 To TreeView1.Nodes.Count
        If mf = LCase$(TreeView1.Nodes(j).Key) Then
            Exit For
        End If
    Next
    
    'Exit if node is not found (nothing is expanded)
    If j > TreeView1.Nodes.Count Then Exit Sub
    
    With TreeView1.Nodes(j)
        .Expanded = True
        .EnsureVisible
        .Selected = True
        Text1 = .Key
        Clipboard.SetText Text1
        ShowInListView .Key
    End With
End Sub