Wyszukiwanie plików w podfolderach za pomocą funkcji API.
• Użyte funkcje API
Funkcja FindFirstFileA wyszukuje pliki i foldery wyłącznie po nazwie pliku, lub wzorcu z użyciem symboli wieloznacznych "?" oraz "*". W wyszukiwaniu nie można określić żadnych atrybutów, takich jak data lub typ pliku. Wyszukiwanie obejmuje długie i krótkie nazwy plików. Jeżeli wywołanie funkcji FindFirstFileA(...) zakończy się sukcesem (zostanie znaleziony plik lub folder) pasujący do wzorca, to funkcja zwraca uchwyt wyszukiwania, a w strukturze WIN32_FIND_DATA informacje o znalezionym pliku lub folderze. Zwrócony uchwyt wyszukiwania możemy użyć w celu wyszukania kolejnego pliku (folderu) za pomocą funkcji FindNextFileA(...) która użyje tego samego filtru wyszukiwania. Przy powodzeniu (znalezieniu pliku lub folderu), funkcja FindNextFileA(...) zwraca wartość niezerową, a w strukturze WIN32_FIND_DATA informacje o znalezionym pliku lub folderze. Aby zakończyć wyszukiwanie powinniśmy zamknąć uchwyt wyszukiwania za pomocą funkcji FindClose(...).
Bardziej szczegółowy opis użytych funkcji i struktur API użytych do wyszukania folderów znajduje się na stronie API. Lista folderów. Na stronie tej jest też przedstawiona funkcja własna plikListSubFoldersApi(...), która zostanie użyta w funkcji wyszukującej pliki w folderze roboczym i jego podfolderach.
Struktura WIN32_FIND_DATA
Gdy funkcja FindFirstFileA(...) lub FindNextFileA(...) zakończy się sukcesem (zostanie znaleziony plik lub folder) pasujący do wzorca, możemy skorzystać ze zwracanej struktury WIN32_FIND_DATA, by pobrać więcej danych o pliku lub folderze. W funkcji fileListSubFoldersAPI(...) wyszukującej podfoldery w folderze roboczym korzystamy z elementu dwFileAttributes tej struktury, by sprawdzić, czy znaleziony obiekt jest folderem, a nie plikiem. W poniżej funkcji plikListFilesW, skorzystamy z elementu dwFileAttributes, by sprawdzić atrybuty pliku. Wyszukiwanie można rozbudować o wyszukiwanie po dacie utworzenia (ftCreationTime), lub dacie modyfikacji (ftLastWriteTime), a także po wielkości pliku (nFileSizeHigh i nFileSizeLow).
Poniżej struktura WIN32_FIND_DATA i krótki opis jej poszczególnych elementów:
- Type WIN32_FIND_DATA
- dwFileAttributes As Long
- atrybuty pliku (ukryty, tylko do odczytu, systemowy lub archiwalny
- ftCreationTime As FILETIME
- data utworzenia pliku
- ftLastAccessTime As FILETIME
- data ostatniego dostępu (otworzenia, modyfikacja lub uruchomienie)
- ftLastWriteTime As FILETIME
- data ostatniej modyfikacji pliku
- nFileSizeHigh As Long
- rozmiar pliku w bajtach, jako najbardziej znacząca wartość DWORD
- nFileSizeLow As Long
- rozmiar pliku w bajtach, jako mniej znacząca wartość DWORD
- dwReserved0 As Long
- zarezerwowane
- dwReserved1 As Long
- zarezerwowane
- cFileName As String * MAX_PATH
- nazwa pliku
- cAlternate As String * 14
- alternatywna nazwa pliku w formacie 8.3
- End Type
Funkcja wyszukująca pliki w folderze roboczym i podfolderach
⊗ Public Function plikListFiles( _ ByVal sFolderName As String, _ sFilesPathRet() As String, _ tpWFDRet() As WIN32_FIND_DATA, _ Optional ByVal sMask As String = "*", _ Optional fSearchInSubFolders As Boolean = True, _ Optional iAttrib As Integer = -1) As Long
- Funkcja plikListFiles(...) wykorzystuje funkcje API w celu znalezienia plików w folderze roboczym i jego podfolderach.
- argumenty:
- sFolderName
- pełna ścieżka określająca roboczy folder, który zostanie przeszukany w celu znalezienia podfolderów.
- sFilesPathRet()
- zwracana ByRef tablica typu String zawierająca pełne nazwy, znalezionych plików folderze sFolderName i jego podfolderach.
- tpWFDRet()
- zwracana ByRef tablica struktur WIN32_FIND_DATA dla każdego pliku. UWAGA! nazwa pliku jest zwracana także w tej strukturze.
- sMask
- argument opcjonalny. Domyślna wartość "*". Maska umożliwiająca szukanie wielu plików przy użyciu symboli wieloznacznych takich jak "*" lub "?"
- fSearchInSubFolders
-
argument opcjonalny. Domyślna wartość True.
Określa rodzaj operacji, jaka będzie wykonywana w trakcie przeszukiwania folderów. Dla domyślnej wartości argumentu fSearchInSubFolders = True przeszukiwane będą wszystkie podfoldery w folderze roboczym. Dla wartości argumentu fSearchInSubFolders = False przeszukiwanie ograniczone zostanie do folderu roboczego, bez przeszukiwania podfolderów. - iAttrib
- Argument opcjonalny. Domyślna wartość -1, dla której wyszukiwane są wszystkie pliki bez względu na posiadany atrybut. Dla innych wartości iAttrib zwracane są jedynie pliki mające zgodne atrybuty.
- zwraca:
-
Zwraca liczbę znalezionych plików w folderze roboczym sFolderName, a w zwracanym ByRef argumencie sFilesPathRet() tablicę, której elementy zawierają pełne ścieżki znalezionych plików bez znaku "/" (backslash) na końcu ścieżki.
- autor: Zbigniew Bratko
- data: 07.01.2018
Option Compare Database Option Explicit #If VBA7 Then Private Declare PtrSafe Function FindFirstFileA Lib "kernel32" _ (ByVal lpFileName As String, _ lpFindFileData As WIN32_FIND_DATA) As LongPtr Private Declare PtrSafe Function FindNextFileA Lib "kernel32" _ (ByVal hFindFile As LongPtr, _ lpFindFileData As WIN32_FIND_DATA) As Long Private Declare PtrSafe Function FindClose Lib "kernel32" _ (ByVal hFindFile As LongPtr) As Long Private Declare PtrSafe Function lstrlenA Lib "kernel32" _ (ByVal lpString As String) As Long #Else Private Declare Function FindFirstFileA Lib "kernel32" _ (ByVal lpFileName As String, _ lpFindFileData As WIN32_FIND_DATA) As Long Private Declare Function FindNextFileA Lib "kernel32" _ (ByVal hFindFile As Long, _ lpFindFileData As WIN32_FIND_DATA) As Long Private Declare Function FindClose Lib "kernel32" _ (ByVal hFindFile As Long) As Long Private Declare Function lstrlenA Lib "kernel32" _ (ByVal lpString As String) As Long #End If Private Const MAX_PATH = 260 Private Const INVALID_HANDLE_VALUE = -1 Private Const FILE_ATTRIBUTE_DIRECTORY = &H10 Public Type FILETIME dwLowDateTime As Long dwHighDateTime As Long End Type Public 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 Public Function plikListFiles( _ ByVal sFolderName As String, _ sFilesPathRet() As String, _ tpWFDRet() As WIN32_FIND_DATA, _ Optional ByVal sMask As String = "*", _ Optional fSearchInSubFolders As Boolean = True, _ Optional iAttrib As Integer = -1) As Long #If VBA7 Then Dim hFindFile As LongPtr Dim hNextFile As LongPtr #Else Dim hFindFile As Long Dim hNextFile As Long #End If Dim tpWFD As WIN32_FIND_DATA Dim sSubfolders() As String Dim sFileName As String Dim iFileAttrib As Integer Dim lCount As Long Dim i As Long ' sprawdź, czy na końcu znajduje się backslach, jeżeli nie to go dopisz If StrComp(Right$(sFolderName, 1), "\", vbBinaryCompare) <> 0 Then sFolderName = sFolderName & "\" End If ' uwzględnij podfoldery If fSearchInSubFolders = True Then If plikListSubFoldersApi(sFolderName, sSubfolders(), "*", True)>0 Then ' dostosuj wymiar tablicy do ilości zwróconych podfolderów ReDim Preserve sSubfolders(0 To UBound(sSubfolders) + 1) Else ' brak podfolderów ReDim Preserve sSubfolders(0 To 0) End If ' dopisz katalog główny bez znaku \ na końcu sSubfolders(UBound(sSubfolders)) = Left$(sFolderName, Len(sFolderName) - 1) Else ' dopisz katalog główny bez znaku \ na końcu ReDim Preserve sSubfolders(0 To 0) sSubfolders(0) = Left$(sFolderName, Len(sFolderName) - 1) End If ' dopisz znaku \ na końcu nazwy podfolderu For i = LBound(sSubfolders()) To UBound(sSubfolders()) sSubfolders(i) = sSubfolders(i) & "\" Next ' przeszukaj folder roboczy i wszystkie podfoldery (dla fSearchInSubFolders = True) For i = LBound(sSubfolders()) To UBound(sSubfolders()) ' utwórz uchwyt wyszukiwania hFindFile = FindFirstFileA(sSubfolders(i) & sMask, tpWFD) ' szukaj następnych plików zgodnych z uchwytem wyszukiwania If hFindFile <> INVALID_HANDLE_VALUE Then Do sFileName = Left$(tpWFD.cFileName, lstrlenA(tpWFD.cFileName)) ' nie uwzględniaj "." i ".." If StrComp(sFileName, ".", vbBinaryCompare) <> 0 And _ StrComp(sFileName, "..", vbBinaryCompare) <> 0 Then ' pobierz atrybuty znalezionego elementu iFileAttrib = tpWFD.dwFileAttributes ' uwzględniamy tylko pliki w folderach If (iFileAttrib And FILE_ATTRIBUTE_DIRECTORY) = 0 Then With tpWFD ' wszystkie pliki If iAttrib = -1 Then ReDim Preserve sFilesPathRet(0 To lCount) ReDim Preserve tpWFDRet(0 To lCount) sFilesPathRet(lCount) = _ sSubfolders(i) & sFileName tpWFDRet(lCount) = tpWFD lCount = lCount + 1 Else ' tylko pliki z wybranymi atrybutem(-ami) - iAttrib If (iFileAttrib And iAttrib) = iAttrib Then ReDim Preserve sFilesPathRet(0 To lCount) ReDim Preserve tpWFDRet(0 To lCount) sFilesPathRet(lCount) = _ sSubfolders(i) & sFileName tpWFDRet(lCount) = tpWFD lCount = lCount + 1 End If End If End With End If End If ' szukaj następnego pliku hNextFile = FindNextFileA(hFindFile, tpWFD) Loop Until hNextFile = 0 ' zamknij uchwyt wyszukiwania hNextFile = FindClose(hFindFile) End If Next plikListFiles = lCount End Function ' Rekurencyjna funkcja plikListSubFoldersApi (...) wykorzystuje funkcje API ' w celu znalezienia wszystkich podfolderów w folderze roboczym. ' argumenty: ' sFolderName: ' pełna ścieżka określająca roboczy folder, który zostanie przeszukany w celu ' znalezienia podfolderów. ' sSubFoldersRet() ' tablica typu String, która przekazywana jest ByRef w kolejnych rekurencyjnych ' wywołaniach funkcji. W każdym wywołaniu uzupełniana jest pełnymi ścieżkami znalezionych ' podfolderów w przekazywanym folderze sFolderNameW ' sMask ' Argument opcjonalny. Domyślna wartość "*". ' Maska umożliwiająca wyszukanie podfolderów przy użyciu symboli wieloznacznych takich ' jak "*" lub "?". Niestety, wyszukiwanie za pomocą wieloznacznika "?" (question mark) ' jest nieco odmienne od działania przy użyciu kryterium Like, gdzie pojedynczy znak "?" ' oznacza jeden znak w ciągu znaków. Ale o tym w „Porównanie z wyszukiwaniem ....” poniżej ' pełnego kodu funkcji plikListSubFoldersApi (...) ' fSearchInSubFolders ' Argument opcjonalny. Domyślna wartość True. ' Określa rodzaj operacji, jaka będzie wykonywana w trakcie przeszukiwania folderów. ' Dla domyślnej wartości argumentu fSearchInSubFolders = True przeszukiwane będą wszystkie ' podfoldery w folderze roboczym. Dla wartości argumentu fSearchInSubFolders = False ' przeszukiwanie ograniczone zostanie do folderu roboczego, bez przeszukiwania podfolderów. ' zwraca: ' Zwraca liczbę znalezionych podfolderów w folderze roboczym sFolderName, a w zwracanym ' ByRef argumencie sSubfoldersRet() tablicę, której elementy zawierają pełne ścieżki ' znalezionych podfolderów bez znaku "/" (backslash) na końcu ścieżki. ' autor: Zbigniew Bratko ' data: 05.01.2018 Public Function plikListSubFoldersApi( _ ByVal sFolderName As String, _ ByRef sSubFoldersRet() As String, _ Optional ByVal sMask As String = "*", _ Optional fSearchInSubFolders As Boolean = True) As Long #If VBA7 Then Dim hFindFile As LongPtr Dim hNextFile As LongPtr #Else Dim hFindFile As Long Dim hNextFile As Long #End If Dim tpWFD As WIN32_FIND_DATA Dim sDirName As String Dim sFullPath As String Dim colSubFolders As Collection Dim lCount As Long Static lRet As Long Dim i As Long ' jeżeli tablica została zainicjowana to ustal ponownie licznik ' (nietypowe sprawdzenie, czy tablica jest zainicjowana ?ToDo?) If (Not sSubFoldersRet) <> -1 Then lCount = UBound(sSubFoldersRet) + 1 End If ' sprawdź, czy na końcu znajduje się backslach, jeżeli nie to go dopisz If StrComp(Right$(sFolderName, 1), "\", vbBinaryCompare) <> 0 Then sFolderName = sFolderName & "\" End If ' zainicjuj kolekcję przechowującą znalezione podfoldery, ' by przekazać je w rekurencyjnym wywołaniu Set colSubFolders = New Collection ' zacznij wyszukiwanie, zwróć uchwyt wyszukiwania hFindFile = FindFirstFileA(sFolderName & sMask, tpWFD) ' zacznij szukaj If hFindFile <> INVALID_HANDLE_VALUE Then Do sDirName = Left$(tpWFD.cFileName, lstrlenA(tpWFD.cFileName)) ' nie uwzględniaj "." i ".." If StrComp(sDirName, ".", vbBinaryCompare) <> 0 And _ StrComp(sDirName, "..", vbBinaryCompare) <> 0 Then ' sprawdź czy znaleziony element jest folderem If (tpWFD.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY) = FILE_ATTRIBUTE_DIRECTORY Then sFullPath = sFolderName & sDirName ' sprawdź, czy przeszukiwać podfoldery If fSearchInSubFolders = True Then ' dodaj do kolekcji rekurencyjnej colSubFolders.Add sFullPath End If ' dodaj do zwracanej tablicy i zwiększ licznik ReDim Preserve sSubFoldersRet(0 To lCount) sSubFoldersRet(lCount) = sFullPath '& "\" lCount = lCount + 1 End If End If ' szukaj następnego podfolderu hNextFile = FindNextFileA(hFindFile, tpWFD) Loop Until hNextFile = 0 ' zamknij uchwyt wyszukiwania hNextFile = FindClose(hFindFile) End If ' sprawdź, czy przeszukiwać podfoldery If fSearchInSubFolders = True Then ' przeszukuj rekurencyjnie podfoldery z kolekcji colSubfolders For i = 1 To colSubFolders.Count Call plikListSubFoldersApi(colSubFolders.Item(i), sSubFoldersRet, _ sMask, fSearchInSubFolders) Next End If If (Not sSubFoldersRet) <> -1 Then plikListSubFoldersApi = UBound(sSubFoldersRet) + 1 End If Set colSubFolders = Nothing End Function
Uwagi.
Fragment kodu oznaczony komentarzem „Nietypowe sprawdzenie, czy tablica jest zainicjowana” wymaga dalszego doprecyzowania. ⇒ToDo⇐
Nietypowe sprawdzenie, czy tablica jest zainicjowana
If (Not sSubFoldersRetW) <> -1 Then
' wykonaj instrukcje
End If