|
|
![]() |
|
||
|
|
|||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Prerequisites |
| None. |
|
|
FindFirstFile: Recursive File Search (minimal code),
as well as all the other recursive search methods on VBnet, relies on two
routines to do the actual searching - a 'SearchForFiles' routine that begins the
enumeration process and recursively calls itself to enumerate lower folders, and
a 'GetFileInformation' routine to perform further checking on the file and add
it to a list.While that dual-method technique remains a flexible way of performing a recursive search for a single file type, the code can be reduced into a single recursive procedure as this page shows. Central to this single-routine method is the declaration of the application's FILE_PARAMS within the general declarations section of the form, rather than inside the calling procedure as other demos use. While purists - those who believe form-level variables are to be avoided - will prefer to continue using the routine-level methods from the other pages, declaring the Type at the form level does increase performance since the type is not continually passed as a parameter to the search routines. In addition, further performance is gained by making two code changes suggested by Romke Soldaat - the first to change the TrimNull function from a pure VB method to a quicker API method using lstrlen, and the second to use the shlwapi.dll API PathMatchSpec(). PathMatchSpec takes a filename and path and returns 1 if the spec is contained in the filename passed, or 0 if not. But an interesting (and as far as I can see undocumented) feature of PathMatchSpec is that it can test a file for multiple files extensions. Therefore, by adding Romke's routine, we can create a fully-functional recursive file search supporting a multiple file-type mask! As mentioned, a customized user-defined Type is used as the mechanism to hold multiple search parameters used by the search routine. This allows for extending the capabilities of a routine by simply adding a new member to the UDT and coding for it (as was done with an additional member for this demo - a value to store the number of files examined (nSearched)). The default search mode coded in the Command1_Click sub is to search only the specified folder. When the "Recurse" button is checked the specified folder and all subfolders under it are searched. When a drive alone is specified as the source path the recursion searches all folders on the drive. Note that no DoEvents() is used, so if doing a drive-wide search your app will become unresponsive for a few seconds while the search takes place. You may want to judiciously add DoEvents() calls to allow the user to move the form or cancel the search. Notes:
|
| BAS Module Code |
| None. |
|
|
| Form Code |
|
|
| Create a new project with a form containing five text boxes (Text1, Text2, Text3, Text4, Text5), a check box (Check1), a list box (List1) and a command button (Command1). Label as desired and add the following code: |
|
|
Option Explicit '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Copyright ©1996-2011 VBnet/Randy Birch, All Rights Reserved. ' Some pages may also contain other copyrights by the author. '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Distribution: You can freely use this code in your own ' applications, but you may not reproduce ' or publish this code on any web site, ' online service, or distribute as source ' on any media without express permission. '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Const vbDot = 46
Private Const MAX_PATH As Long = 260
Private Const INVALID_HANDLE_VALUE = -1
Private Const vbBackslash = "\"
Private Const ALL_FILES = "*.*"
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 * MAX_PATH
cAlternate As String * 14
End Type
Private Type FILE_PARAMS
bRecurse As Boolean
nCount As Long
nSearched As Long
sFileNameExt As String
sFileRoot As String
End Type
Private Declare Function FindClose Lib "kernel32" _
(ByVal hFindFile As Long) As Long
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 GetTickCount Lib "kernel32" () As Long
Private Declare Function lstrlen Lib "kernel32" _
Alias "lstrlenW" (ByVal lpString As Long) As Long
Private Declare Function PathMatchSpec Lib "shlwapi" _
Alias "PathMatchSpecW" _
(ByVal pszFileParam As Long, _
ByVal pszSpec As Long) As Long
Private fp As FILE_PARAMS 'holds search parameters
Private Sub Form_Load()
Text1.Text = "c:\"
Text2.Text = "*.doc; *.xls; *.ppt"
Command1.Caption = "Begin Search"
End Sub
Private Sub Command1_Click()
Dim tstart As Single 'timer var for this routine only
Dim tend As Single 'timer var for this routine only
Text3.Text = ""
Text4.Text = ""
Text5.Text = ""
List1.Clear
List1.Visible = False
With fp
.sFileRoot = QualifyPath(Text1.Text) 'start path
.sFileNameExt = Text2.Text 'file type(s) of interest
.bRecurse = Check1.Value = 1 'True = recursive search
.nCount = 0 'results
.nSearched = 0 'results
End With
tstart = GetTickCount()
Call SearchForFiles(fp.sFileRoot)
tend = GetTickCount()
List1.Visible = True
Text3.Text = Format$(fp.nSearched, "###,###,###,##0")
Text4.Text = Format$(fp.nCount, "###,###,###,##0")
Text5.Text = FormatNumber((tend - tstart) / 1000, 2) & " seconds"
End Sub
Private Sub SearchForFiles(sRoot As String)
Dim WFD As WIN32_FIND_DATA
Dim hFile As Long
hFile = FindFirstFile(sRoot & ALL_FILES, WFD)
If hFile <> INVALID_HANDLE_VALUE Then
Do
'if a folder, and recurse specified, call
'method again
If (WFD.dwFileAttributes And vbDirectory) Then
If Asc(WFD.cFileName) <> vbDot Then
If fp.bRecurse Then
SearchForFiles sRoot & TrimNull(WFD.cFileName) & vbBackslash
End If
End If
Else
'must be a file..
If MatchSpec(WFD.cFileName, fp.sFileNameExt) Then
fp.nCount = fp.nCount + 1
List1.AddItem sRoot & TrimNull(WFD.cFileName)
End If 'If MatchSpec
End If 'If WFD.dwFileAttributes
fp.nSearched = fp.nSearched + 1
Loop While FindNextFile(hFile, WFD)
End If 'If hFile
Call FindClose(hFile)
End Sub
Private Function QualifyPath(sPath As String) As String
If Right$(sPath, 1) <> vbBackslash Then
QualifyPath = sPath & vbBackslash
Else
QualifyPath = sPath
End If
End Function
Private Function TrimNull(startstr As String) As String
TrimNull = Left$(startstr, lstrlen(StrPtr(startstr)))
End Function
Private Function MatchSpec(sFile As String, sSpec As String) As Boolean
MatchSpec = PathMatchSpec(StrPtr(sFile), StrPtr(sSpec))
End Function
|
| Comments |
| Before running, assure that any hard-coded paths reflect accurate paths on your system.
It must also be noted that, since this example uses the listbox to return the results, on systems containing many files you may, eventually, hit the listbox item limit of 32k items under an non-NT-based system. While there is no practical or reliable way to extend the number of items a listbox can contain on a Win9x system (without resorting to an owner-drawn control), you can increase the number of files the method is capable storing (if exceeding the size of a Long), by declaring the appropriate variables as Currency instead of Long. Note: While it may be convenient to utilize VB's built-in constants in place of the FILE_ATTRIBUTE_* API values, care must be taken. There is a difference between related constant values that may cause unexpected performance at some point. For example, the constant 'vbNormal' is defined as having a value of 0, whereas the API FILE_ATTRIBUTE_NORMAL has a value of &H80 (decimal 128). |
|
|
|
|
|
|||||
|
|||||
|
|
|||||
|
Copyright ©1996-2011 VBnet and Randy Birch. All Rights Reserved. |
![]() |