今回は前回記事の一部修正です。
コード記述中にずっとモヤモヤしていたことが解消できたのでわずかですが修正公開です。
今回は再帰処理を利用して指定フォルダ内だけでなく、フォルダ内のフォルダの中身を検索できるようにしてみる。
再帰処理でフォルダ内検索
前回は1つのプロシージャで実現したが、今回は3つのプロシージャに分割して機能を実現した。
・FileSearch・・・前処理や検索結果のセルへの転記を行う
・DirSearch関数・・・前処理やCollectionに蓄積した検索結果を配列へ変換する
・Dir_Search・・・DirSearch内で呼び出される。実際のDir関数での検索を行う。
FileSearch
Option Explicit Sub FileSearch() Cells.Clear Dim myVar As Variant myVar = DirSearch("C:\Users\ユーザー名\Desktop\", True) 'DirSearchの返り値が空配列でなければセルに検索結果を転記 If UBound(myVar) <> -1 Then Range("A1").Resize(UBound(myVar), 1).Value = myVar End If End Sub
ここでは次に紹介するDirSearch関数を呼び出して、検索開始フォルダとサブフォルダ内検索の有無を指定している。
DirSearch関数は検索結果に関わらず、返り値として配列を返すようにしている。
DirSearch関数の返り値に対してUbound関数を使用し、返り値が-1(空配列)であれば検索結果が0であるため処理無しとする。
何らかの検索結果がある場合は2次元配列が返ってきているので、セルサイズを合わせて一括で転記している。
DirSearch関数
'---------------------------------------------------------------------- Rem DirSearch関数 Rem 第1引数 FolderPath:検索対象とするフォルダパスを指定する Rem 第2引数 SubFolderSearch:フォルダ内のサブフォルダ内を検索対象とするかどうか Rem 返り値 見つかったファイルのフルパス一覧を2次元配列として返す。 Rem 1件もファイルが見つからなかった場合は空配列を返す。 Rem 関数内sub Dir_Searchが溜め込んだ検索結果コレクションを配列へ転記する '---------------------------------------------------------------------- Function DirSearch(FolderPath As String, SubFolderSearch As Boolean) As Variant Dim myCol As Collection Set myCol = New Collection Dir_Search FolderPath, SubFolderSearch, myCol Dim myItem As Variant '検索結果を蓄積していたCollectionから配列へ転記してからセルへ一括転記 If myCol.Count = 0 Then DirSearch = Array() Else Dim myVar As Variant Dim i As Long: i = 1 ReDim myVar(1 To myCol.Count, 1 To 1) For Each myItem In myCol myVar(i, 1) = myItem i = i + 1 Next DirSearch = myVar End If Set myCol = Nothing End Function
DirSearch関数は次に紹介するDir_SearchがCollectionオブジェクトに蓄積した検索結果を配列へと変換して外部へ返す役割を担っている。
Dir_Search
'--------------------------------------------------------------------------------------- Rem Dir_Search Rem 第1引数 FolderPath:検索対象とするフォルダパスを指定する Rem 第2引数 SubFolderSearch:フォルダ内のサブフォルダ内を検索対象とするかどうか Rem 第3引数 myCol:Collectionオブジェクト。呼び出し元DirSearchより渡されたCollecitonへ検索結果を蓄積していく Rem Dir関数を利用してファイル・フォルダを検索。検索結果をパブリックCollectionへ蓄積していく。 '--------------------------------------------------------------------------------------- Private Sub Dir_Search(FolderPath As String, SubFolderSearch As Boolean, ByRef myCol As Collection) Dim myFileName As String Dim myFolderCol As Collection Set myFolderCol = New Collection 'ファイルとフォルダを検索対象とする myFileName = Dir(FolderPath, VbFileAttribute.vbSystem + VbFileAttribute.vbHidden + VbFileAttribute.vbDirectory) '検索対象が見つからなくなるまでループ処理 Do Until myFileName = "" '見つかったものが該当フォルダ及び親フォルダの場合は処理なし If myFileName = "." Or myFileName = ".." Then '見つかったものがフォルダ内フォルダの場合はフォルダ一覧コレクションに登録 ElseIf GetAttr(FolderPath & myFileName) And vbDirectory Then myFolderCol.Add FolderPath & myFileName '上記2つ以外はファイル一覧コレクションに登録 Else myCol.Add FolderPath & myFileName End If myFileName = Dir Loop Rem サブフォルダー内検索処理 '引数SubFolderSearchがTrueの場合は再帰処理で中を検索していく Dim myItem As Variant If SubFolderSearch = True Then For Each myItem In myFolderCol Dir_Search myItem & "\", SubFolderSearch, myCol Next End If End Sub
本件の中核となるDir_Search。
実際にDir関数でファイル・フォルダの検索を行い検索結果をCollectionオブジェクトへ蓄積していく。
Dir関数はフォルダ名・ファイル名のみを返すので、Collectionへ追加する際は親フォルダ部分を追加してから行う。
Dir関数の検出結果がフォルダなのかファイルなのかを判定するためにGeAttr関数とビットアンド演算を利用している。
サブフォルダ以内の検索はDir_Search内からDir_Search自身を呼び出す再帰処理で実現している。
まとめ
冒頭に記載した全開コードのモヤモヤとはCollectionオブジェクトのモジュールレベル変数宣言するためにDirSearch関数の外で実行していたことでした。
つまりDirSearch関数を使用する時に関数の外で、関数に必要な変数宣言を行っていることがどうにも腑に落ちていませんでした。
モヤモヤしている時に以前の記事のコメントで教えて頂いたことを思い出して修正を行った次第。
修正箇所は
・冒頭のモジュールレベル変数CollectionをDirSearch関数へ内包。
・Dir_Searchに第3引数を追加してCollectionオブジェクトを参照渡しする。
あぁスッキリした。
教えて頂いたコメントが有る記事