|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Visual Basic File API Routines FindFirstFile: Fast Directory File Count |
||
Posted: | Thursday September 9, 1999 | |
Updated: | Monday December 26, 2011 | |
Applies to: | VB4-32, VB5, VB6 | |
Developed with: | VB6, Windows NT4 | |
OS restrictions: | None | |
Author: | VBnet - Randy Birch | |
Related:
|
FindFirstFile: Changing File and/or Folder Attributes Recursively
FindFirstFile: Determining the Oldest Folder (Recursive) FindFirstFile: Fast Directory File Count FindFirstFile: Extract Filename from a Full Path FindFirstFile: Performance Comparison - FSO vs. API FindFirstFile: Comparison of FindFirstFile and SearchTreeForFile FindFirstFile: Save a Recursive Search of All Drives to Disk FindFirstFile: Save a Recursive Search of Specified Drives to Disk GetFileVersionInfo: File Search and File Property Info |
|
Prerequisites |
None. |
|
The
routines here demonstrates how to use FindFirstFile, FindNextFile and GetFileAttributes to selectively count all files matching a
given criteria (filespec) and/or file attribute. This makes the routine ideal for counting all files in a given folder (*.*), or for
determining hidden or system files, or directories on a given path. Although the majority of the code is repetitive amongst routines, it is presented this way to isolate each routine for simplicity. In addition, the code (as written) is not recursive, so will only determine the number of files under the initial starting directory. Other VBnet pages show the modifications required to use recursive functions to obtain the total files under the start path. |
BAS Module Code |
None. |
|
Form Code |
Add three command buttons (Command1, Command2, Command3), and a listbox (List1) to the form, along with 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 INVALID_HANDLE_VALUE As Long = -1 Private Const MAX_PATH As Long = 260 Private Const FILE_ATTRIBUTE_READONLY As Long = &H1 Private Const FILE_ATTRIBUTE_HIDDEN As Long = &H2 Private Const FILE_ATTRIBUTE_SYSTEM As Long = &H4 Private Const FILE_ATTRIBUTE_DIRECTORY As Long = &H10 Private Const FILE_ATTRIBUTE_ARCHIVE As Long = &H20 Private Const FILE_ATTRIBUTE_NORMAL As Long = &H80 Private Const FILE_ATTRIBUTE_TEMPORARY As Long = &H100 Private Const FILE_ATTRIBUTE_COMPRESSED As Long = &H800 Private Const FILE_ATTRIBUTE_ALL As Long = FILE_ATTRIBUTE_READONLY Or _ FILE_ATTRIBUTE_HIDDEN Or _ FILE_ATTRIBUTE_SYSTEM Or _ FILE_ATTRIBUTE_ARCHIVE Or _ FILE_ATTRIBUTE_NORMAL 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 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 GetFileAttributes Lib "kernel32" _ Alias "GetFileAttributesA" _ (ByVal lpFileName As String) As Long Private Declare Function lstrlenW Lib "kernel32" _ (ByVal lpString As Long) As Long Private Sub Command1_Click() Dim sSource As String Dim sFileType As String Dim numFiles As Long 'initialize values sSource = "c:\windows\system32\" sFileType = "*.*" 'get the count numFiles = FilesCountAll(sSource, sFileType) MsgBox numFiles & " files found matching " & sSource & sFileType, _ vbOKOnly Or vbInformation, _ "VBnet FindFirstFile File Count Demo" End Sub Private Sub Command2_Click() Dim sSource As String Dim sFileType As String Dim dwAttributes As Long Dim numFiles As Long 'initialize values sSource = "c:\windows\" sFileType = "*.*" 'set the attribute(s) to search for dwAttributes = FILE_ATTRIBUTE_NORMAL Or FILE_ATTRIBUTE_ARCHIVE 'get the count numFiles = FilesCountByAttribute(sSource, sFileType, dwAttributes) MsgBox numFiles & " files found matching " & _ sSource & sFileType & " with attribute(s) " & _ GetAttributeString(dwAttributes), _ vbOKOnly Or vbInformation, _ "VBnet FindFirstFile File Count Demo" End Sub Private Sub Command3_Click() Dim sSource As String Dim sFileType As String Dim dwAttributes As Long Dim numFiles As Long 'initialize values sSource = "c:\windows\" sFileType = "*.*" 'set the attribute(s) to search for dwAttributes = FILE_ATTRIBUTE_ARCHIVE 'clear the list and hide to 'prevent updating until the 'search is complete With List1 .Clear .Refresh .Visible = False End With 'get the list and count numFiles = FilesListByAttribute(sSource, sFileType, dwAttributes) With List1 .Visible = True .ListIndex = 37 End With MsgBox numFiles & " files found matching " & _ sSource & sFileType & " with attribute(s) " & _ GetAttributeString(dwAttributes), _ vbOKOnly Or vbInformation, _ "VBnet FindFirstFile File Count Demo" End Sub Private Function FilesCountAll(sSource As String, sFileType As String) As Long Dim wfd As WIN32_FIND_DATA Dim hFile As Long Dim fCount As Long 'Start searching for files in 'sSource by obtaining a file 'handle to the first file matching 'the filespec passed hFile = FindFirstFile(sSource & sFileType, wfd) If hFile <> INVALID_HANDLE_VALUE Then 'must have at least one, so ... Do 'increment the counter and 'find the next file - see comments below If (Asc(wfd.cFileName) <> vbDot) Then FilesCountAll = FilesCountAll + 1 Loop Until FindNextFile(hFile, wfd) = 0 End If 'Close the search handle Call FindClose(hFile) End Function Private Function FilesCountByAttribute(sSource As String, _ sFileType As String, _ dwAttributes As Long) As Long Dim wfd As WIN32_FIND_DATA Dim hFile As Long hFile = FindFirstFile(sSource & sFileType, wfd) If (hFile <> INVALID_HANDLE_VALUE) Then Do 'compare the file attributes 'against the passed flag(s). 'No need to remove trailing nulls. If (dwAttributes And GetFileAttributes(sSource & wfd.cFileName)) And _ (Asc(wfd.cFileName) <> vbDot) Then 'increment the counter and 'find the next file matching - see comments below 'the file spec FilesCountByAttribute = FilesCountByAttribute + 1 End If Loop Until FindNextFile(hFile, wfd) = 0 End If 'Close the search handle Call FindClose(hFile) End Function Private Function FilesListByAttribute(sSource As String, _ sFileType As String, _ dwAttributes As Long) As Long Dim wfd As WIN32_FIND_DATA Dim hFile As Long 'Start searching for files 'in the Source directory hFile = FindFirstFile(sSource & sFileType, wfd) If (hFile <> INVALID_HANDLE_VALUE) Then Do 'compare the file attributes 'against the passes flag(s) If (dwAttributes And GetFileAttributes(sSource & wfd.cFileName)) = dwAttributes And (Asc(wfd.cFileName) <> vbDot) Then 'increment the counter and find 'the next file matching the file spec '- see comments below List1.AddItem TrimNull(wfd.cFileName) FilesListByAttribute = FilesListByAttribute + 1 End If Loop Until FindNextFile(hFile, wfd) = 0 End If 'Close the search handle Call FindClose(hFile) End Function Private Function GetAttributeString(attr As Long) As String Dim tmp As String If attr And FILE_ATTRIBUTE_ARCHIVE Then tmp = tmp & "ARCHIVE " If attr And FILE_ATTRIBUTE_NORMAL Then tmp = tmp & "NORMAL " If attr And FILE_ATTRIBUTE_HIDDEN Then tmp = tmp & "HIDDEN " If attr And FILE_ATTRIBUTE_READONLY Then tmp = tmp & "READONLY " If attr And FILE_ATTRIBUTE_SYSTEM Then tmp = tmp & "SYSTEM " If attr And FILE_ATTRIBUTE_TEMPORARY Then tmp = tmp & "TEMPORARY " If attr And FILE_ATTRIBUTE_COMPRESSED Then tmp = tmp & "COMPRESSED " If attr And FILE_ATTRIBUTE_DIRECTORY Then tmp = tmp & "DIRECTORY " GetAttributeString = tmp End Function Private Function TrimNull(startstr As String) As String TrimNull = Left$(startstr, lstrlenW(StrPtr(startstr))) End Function |
Comments |
Save the project. Before running, set the desired paths
and file specs in the command click subs. Each routine uses the code "(Asc(wfd.cFileName) <> vbDot)" to prevent counting the two special and hidden directories ('.' and '..') present in every folder. The Asc() code compares the ASCII value of the first letter of the retrieved file against the ASCII value of a period (ASCII 46), skipping the item if a match. The accuracy of this method is predicated on the presumption that users don't *begin* their file names or folders with a period. If you believe this presumption is too, well, presumptuous, there are other options available to eliminate these two system files from the file count, each with corresponding caveats. 1) probably the simplest method is to subtract 2 from the file count if the enumeration was performed on a folder other than the root drive, or subtract 1 if a drive was specified. All subfolders have the '.' (current folder) and '..' (parent folder) members; root folders (drive letters) only have the '.' (current folder) item. 2) Both '.' and '..' have the FILE_ATTRIBUTE_DIRECTORY attribute bit set, thus the If-Then could perform a directory test. And since the routine is to count *files* this won't impact the immediate functionality. It will however prevent the ability to use the same code to perform a directory check if the FILE_ATTRIBUTE_DIRECTORY flag is passed: If (wfd.dwFileAttributes And vbDirectory) <> FILE_ATTRIBUTE_DIRECTORY Then ... 3) Test the items as strings - the least efficient method but the most specific: If (Left$(wfd.cFileName, 1) <> ".") And (Left$(wfd.cFileName, 2) <> "..") Then ... 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. |