フォルダ内一覧化

フォルダ内を一覧化するアプリを作ろう その4 ExcelVBA

フォルダ内一覧化
スポンサーリンク

みなさまは片付け得意でしょうか?
私は片付けが非常に苦手です。。。

自分の部屋も、気づいたらいつもごちゃごちゃになっていきます。。。

実生活もそうですが、パソコンの中も同じように、
気づいたらごちゃごちゃになって、目当てのファイルを探すのも一苦労
です。。。

同じような経験をされている方もおられるのではないでしょうか。

今回はそんな方々向けに、家庭用・仕事用、どちらでも使えるような
『フォルダ一覧化マクロ』を作っていきます。

パソコン内を整理する、というよりも、
ごちゃごちゃのままでも目当てのファイルを見つけ出す!という思想です♪
一緒に作って作業効率アップをしていきましょう!

【本記事の目標】

好みの階層まで検索できるようにしよう
(Public変数を使いこなそう)

その1~その3までをまだ読まれていない方は、過去の投稿もご確認ください。



好みの階層で検索を止めるには?

前回のおさらい

前回までに作成したマクロは、イメージ図で書くと下記のような状態になっています。

再帰呼び出しのプロシージャの中の条件式は、
 そのフォルダの中にファイルがあるか?フォルダがあるか?
という条件だけが書かれています。

そのため、メインプロシージャから再帰呼び出しをするプロシージャを呼び出した後、
検索したいフォルダの中に、
 ・ファイルがあればファイルの名前・パス等をExcelシートに記入する
 ・フォルダがあれば、そのフォルダのパスを引数として再帰呼び出しを実行
という流れで進んでいくため、検索したいフォルダの中がどれだけ深い階層になっていたとしても、
フォルダの中にあるのがファイルだけ、という状態にならない限り、
全てのフォルダをチェックするまで再起呼び出しを繰り返し動き続けます。


実際にマクロを動かしたときにファイルを調べていくイメージは下記のような感じで調べていきます。
(番号順に進んでいき、青矢印はファイルのチェック、赤矢印は再帰呼び出しを表しています)

やりたいこととその対策

これはこれで、全てをチェックするという意味では使い勝手がいいのですが、
あまりに深い階層まであるフォルダだと、検索が終わるまで非常に時間がかかってしまう場合があります。

そんな時に、「できれば最初の4階層ぐらいで、なんとなくのフォルダ構成とかファイル構成が分かればいいんだよね~」という要望も出てくるかと思います。

じゃあそういった時にはどうすればいいのか?


答えは簡単で、
 条件式をもう一つ追加し、任意の階層まで繰り返したら再帰呼び出しを止める
という部分を再帰呼び出しのプロシージャ内に追加してあげればいいのです。


仮に今見ている階層が『KaisouNum』という変数入れることができたと仮定し、
止めたい階層を『StopNum』という変数に入れたとすると、
上記の条件式は、
 If KaisouNum <= StopNum then
  (再起呼び出しを含む実行部分)
 End If

と書くことができるので、再帰呼び出しのプロシージャは下記のようになります。

Option Explicit

Sub FolderSearchSub(SearchFolderPath2 As Variant)
'再帰呼び出しをするプロシージャ


'参照設定を使用する場合(fso1は任意に設定OK)
Dim fso1 As Scripting.FileSystemObject      'FileSystemObjectの変数
Set fso1 = New Scripting.FileSystemObject

'For Eachで使う変数の定義
Dim FilesList1 As Variant
Dim FoldersList1 As Variant

'ファイルの情報を一旦入れておくための変数
Dim FileName1 As String
Dim FilePath1 As String
Dim FileExtensionName1 As String
Dim FileChangeDate As Variant

'シートの最終行の変数
Dim LastRow1 As Integer

If KaisouNum <= StopNum Then

'=====再帰呼び出しを含む実行部分======
'Filesコレクションの操作
For Each FilesList1 In fso1.GetFolder(SearchFolderPath2).Files

    FileName1 = fso1.GetFile(FilesList1).Name
    FilePath1 = fso1.GetFile(FilesList1).Path
    FileChangeDate = fso1.GetFile(FilesList1).DateLastModified
    FileExtensionName1 = fso1.GetExtensionName(FilePath1)
    
    'データをシートに記載
    With ThisWorkbook.Sheets("検索結果一覧")
        LastRow1 = .Cells(.Rows.Count, 1).End(xlUp).Row
        LastRow1 = LastRow1 + 1
        
        .Cells(LastRow1, 1).Value = FileName1
        .Cells(LastRow1, 2).Value = FilePath1
        .Cells(LastRow1, 3).Value = FileExtensionName1
        .Cells(LastRow1, 4).Value = FileChangeDate
    End With
Next FilesList1


'SubFoldersコレクションの操作
For Each FoldersList1 In fso1.GetFolder(SearchFolderPath2).SubFolders

    '自己プロシージャを呼び出している部分
    Call FolderSearchSub(fso1.GetFolder(FoldersList1).Path)
    
Next FoldersList1

'=====再帰呼び出しを含む実行部分の終わり======

End If


End Sub

こうしておけば、今見ている階層の『KaisouNum』が止めたい階層の『StopNum』より小さいときは再帰呼び出しを実行していきますが、
逆に大きくなった時は、【再帰呼び出しを含む実行部分】をまるっと飛ばしてプロシージャが終わり、今の階層にファイルやフォルダの有無にかかわらず、1つ上の階層の別のフォルダを探しに行きます。

上記のファイルを調べていくイメージ図で言うと、
仮に3階層目で止めるようにしていた場合、
6番の再帰呼び出しを実行しますが、7番のファイルチェック等をせずに上の階層に戻り、8番の再起呼び出しを実行します。
また10番の再起呼び出しを実行しますが、11~13番は実行せずに上の階層に戻り、
14番の再起呼び出しを実行しますが、15番のファイルチェック等をせずに上の階層に戻り、16番を実行する。
といった動きをするようになります。



対策を実行する上での課題

これだけ??
じゃあ後は今見ている階層を『KaisouNum』に入れられるようにすれば終わりじゃない?

と思われるかもしれませんが、
今回の一番の課題はそこです!

今見ている階層をどうやって確認するようにしたらいいのか?


多少マクロを触ったことがある方はひょっとしたら思いつくかもしれませんが、
一番簡単なのは、カウンターを設置することです。

再帰呼び出しは結局のところ繰り返し作業なので、
何回繰り返したか? というのが分かれば階層を確認することができるのです。

カウンターというのは、
For文などの繰り返しを行っている場合に、
何回繰り返しを行ったかを確認できるように入れる変数で、
例えば下記のように1~10までを1つずつ足していくようなプログラムを組んだ時、

Dim i as integer
Dim Kotae as integer

For i = 1 to 10
    Kotae = Kotae + i
next i

For文の中を1回実行するたびに「1」を足す『Counter1』という変数を入れておけば、
この『Counter1』という変数がカウンターとしての役割を持ちます。

Dim i as integer
Dim Kotae as integer
Dim Counter1 as integer

Counter1 = 0
For i = 1 to 10
    Kotae = Kotae + i
    Counter1 = Counter1 + 1
next i

For文の場合は繰り返す回数が決まっているので、そのままカウンターを入れる意味はあまりないですが、For文の中にIf文が入っていて、If文を実行した回数を確認したい、という場合はカウンターを付ける意味が出てきます。

じゃあカウンターの変数を定義して、
再帰呼び出しを実行する部分にカウントする部分をつけていたらいいんじゃない?

というと、そうでもないです。

前回の記事の中でも少し書きましたが、再帰呼び出しを実行したとき、最初に実行していたプロシージャと再帰呼び出しで実行したプロシージャ、それぞれで定義している変数は別のものとして認識されます。
なので、プロシージャの中でカウンターの変数を定義しても、再帰呼び出しをするたびに別物として認識されるカウンター変数が生成され行くだけで、繰り返しを行った回数をカウントしてくれません

これを回避しようと思うと、カウンターの変数の適用範囲を広げて、
どのプロシージャでも共通で使えるようにすればいいのです。



変数の定義と適用範囲

そもそも変数を定義する際、これまでは『Dim』だけを使っていましたが、それ以外にも『Private』『Public』というコードがあります。
これらのコードによって、その変数が使える領域、
いわゆる『適用範囲』というのが変わってきます。

下記でそれぞれのコードとその適用範囲を説明します。

Dim

これまでも何度が使っていましたが、『Dim』の適用範囲は
 プロシージャ内 もしくは モジュール内
にのみ適用されます。

どちらの範囲に適用されるかについては、定義をどこで行ったかによって変わります。

プロシージャ内で変数の定義を宣言した場合は、そのプロシージャの中でのみ適用されます。
なので、そのプロシージャが終了すると変数の定義は無効になります。
これまでは上記の方法で変数を定義していました。

ただ、変数の定義はプロシージャ内だけでなく、プロシージャの外でも宣言が可能なのです。
モジュールの最初、プロシージャの外で変数の定義を宣言した場合、その変数はそのモジュールの中にある全てのプロシージャで適用されます。
また、プロシージャ内での定義とは異なり、モジュール内に適用された変数はマクロの実行が終了するまで有効です。

Private

Dimに近い変数定義のためのコードとして『Private』があります。
使い方はDimと同じで、
 Private 変数名 As データ形式
という形で使います。

この『Private』の適用範囲は
 モジュール内
にのみ適用されます。
なので、変数を定義する場所はプロシージャの外、主にモジュールの最初に宣言します。
適用範囲の広さはDimと近いですが、Dimと異なりプロシージャ内での変数の定義はできません

Public

最も適用範囲が広いコードとして『Public』があります。
他のプログラミング言語では『グローバル変数』という場合もあります。
使い方はDimやPublicと同じで、
 Public 変数名 As データ形式
という形で使います。

この『Public』の適用範囲は
 そのExcelファイルの中の全てのモジュール・全てのプロシージャ
に適用されます。
変数を定義する場所は『Private』の場合と同じ、主にモジュールの最初に宣言しますが、
どのモジュールで記載しても、等しく全てのモジュールに適用されます。


適用範囲が一番広いので、「大は小を兼ねる」ということで全ての変数を『Public』にすれば楽なのでは、と思われるかもしれませんが、色々と弊害があります。

主な弊害として、
1つはデータがプロシージャ間を行き来してもデータが残り続ける点です。
例えばFor文で繰り返すためだけの変数として「i」という変数を『Public』で宣言してしまうと、
この「i」を使ったコードが複数回、複数のモジュール・プロシージャで使われていた時、現時点でどのデータが「i」という変数に入っているのかが把握しづらくなり、マクロを動かしたときのエラーを誘発する危険があります。

もう1つはどの変数がどこで宣言されているかわかりづらくなる点です。
1つのモジュールに記載しておけば全てのモジュールに適用されるので、
思いついたときに好きな場所で宣言ができてしまいます。しかし好き勝手に宣言してしまうと、誤って同じ変数名の変数を定義してしまう可能性が高くなり、エラーが発生しやすくなります。

ですので、基本は『Dim』もしくは『Private』を使用して、必要なものだけ『Public』を使うのが、エラーを回避する意味でも有用なのでオススメです。



シート・マクロの修正

上記で長々と説明しましたが、結局のところやりたい「任意の階層で検索を止める」ためには、
 ・階層を確認する条件式を追加する(上記で一度コードを書いてます)
 ・条件式にはカウンター変数を入れるようにする
 ・カウンター変数は『Public』で宣言しておく
(グローバル変数にしておく)
という3つを実施すれば良いのです。

また、今回は止めたい階層を任意で変えられるようにしておきたいので、
止めたい階層を入力する部分も必要です。

それでは、まずは止めたい階層を入力する部分をシート上に作った後、
実際のコードを書いていきましょう。

階層指定を入力する部分を作る

階層指定を入力する部分は、シート上にあればどこでもOKです。
今回は”D4″セルに入力できるようにしました。

あとはこの部分の数値を『StopNum』という変数に入れておけばOKです。

マクロの修正

指定階層を変数に入力(Public変数)

検索を止める指定階層の変数名は、上記で書いたコードで『StopNum』という名前にしていたので、そのまま使うようにします。

この変数は再帰呼び出しをするプロシージャの中で『Dim』を使って定義をしてあげても良いのですが、そうすると再帰呼び出しを実行するたびにExcelシートの”D4”セルの値を取りにいかないといけなくなります。
今はそれほど複雑なマクロではないのでほとんど影響はないですが、マクロが複雑になってくると、頻繁にExcelシートのデータを取りに行くようにしてあると、マクロの動きが遅くなってしまいます。
なので、シートへのアクセスはできる限り最小限にしておいた方が良いです。

せっかく今回『Public』変数の説明をしましたので、こちらも『Public』で定義しましょう。

『Public』で変数を定義する場合、できれば一番メインとなるモジュールの最初に記載していた方が、後々確認しやすくなりますので、下記のようにメインプロシージャの上で変数を定義し、
メインプロシージャの中で”D4”セルのデータを取りに行くようにしました。

カウンターの設定(Public変数)

次にカウンターの設定です。

カウンター変数も上の方で『KaisouNum』という名前にしていたので、それをそのまま使います。
変数を定義する場所も、Public変数ですので上記と同じようにメインプロシージャの上で宣言します。

また、変数を定義した直後は基本的にその変数の中には何も入っていないはずですが、
念のため最初に
 KaisouNum = 0
というコードを入れて初期化しておいた方がより安心です。

あとはこのカウンター変数を使ってどの階層にいるかを確認できるようにしてあげれはOKです。

再帰呼び出しを実行した回数が、そのまま今いる階層になりますので、再帰呼び出しのプロシージャの一番最初に、
 KaisouNum = KaisouNum + 1
というコードを入れておいて、階層の数をカウントしてあげればいいのです。

ただこれだけだと、階層を潜っていく方はカウントできますが、逆に帰ってくる(上の階層に戻る)場合にカウントができなくなります。
帰ってくるときはどういったときかと言うと、再帰呼び出しのプロシージャの中の繰り返し検索が全て終わって実行が終了したタイミングです。
なので、再帰呼び出しのプロシージャの一番最後に、
 KaisouNum = KaisouNum - 1
というコードを入れておいて、階層の数を逆に減らすカウントをしてあげればいいのです。

マクロの修正2(検索開始時にシートの初期化)

上記まででやりたいことは、ほぼ全て完了です。
ただ、ここまでは入力することしか書いてきていないので、
実際に動かしてもらうと分かりますが、2回検索を実行すると、2回目の検索結果は1回目の検索結果の下に表示されてしまいます。

なので、検索を実行するたびに、最初にExcelシートを初期化しておく必要があります。

シートの初期化は簡単で、今のシートの最終行を確認して、その行と6行目の間のデータを削除すればOKなので、
 Dim LastRow1 As Integer
 With ThisWorkbook.Sheets(“検索結果一覧”)
  LastRow1 = .Cells(.Rows.Count + 1, 1).End(xlUp).Row
  .Range(.Cells(6, 1), .Cells(LastRow1, 4)).ClearContents
 End With

このコードをメインプロシージャの中に入れてあげればOKです。



完成!

これでフォルダ検索マクロは完成です!!

最終的なコードは下記のようになりました。

メインプロシージャ

Option Explicit

'Public変数の定義場所
Public StopNum As Integer   '指定階層
Public KaisouNum As Integer 'カウンター変数


Sub FolderSearchMain()
'フォルダ検索のメイン部分

'Excelシートの初期化
Dim LastRow1 As Integer
With ThisWorkbook.Sheets("検索結果一覧")
    LastRow1 = .Cells(.Rows.Count, 1).End(xlUp).Row
    .Range(.Cells(6, 1), .Cells(LastRow1 + 1, 4)).ClearContents
End With

'検索したいフォルダのフルパスを入れる変数
Dim SearchFolderPath1 As String
SearchFolderPath1 = ThisWorkbook.Sheets("検索結果一覧").Range("B1").Value

'検索を止める階層を入力
StopNum = ThisWorkbook.Sheets("検索結果一覧").Range("D4").Value

'カウンター変数の初期化
KaisouNum = 0

'再帰呼び出しするプロシージャを呼び出す部分。
'引数に、検索したいフォルダのフルパスを与えています。
Call FolderSearchSub(SearchFolderPath1)


End Sub

再帰呼び出しのプロシージャ

Option Explicit

Sub FolderSearchSub(SearchFolderPath2 As Variant)
'再帰呼び出しをするプロシージャ

'階層数をカウントアップ
KaisouNum = KaisouNum + 1

'参照設定を使用する場合(fso1は任意に設定OK)
Dim fso1 As Scripting.FileSystemObject      'FileSystemObjectの変数
Set fso1 = New Scripting.FileSystemObject

'For Eachで使う変数の定義
Dim FilesList1 As Variant
Dim FoldersList1 As Variant

'ファイルの情報を一旦入れておくための変数
Dim FileName1 As String
Dim FilePath1 As String
Dim FileExtensionName1 As String
Dim FileChangeDate As Variant

'シートの最終行の変数
Dim LastRow1 As Integer

If KaisouNum <= StopNum Then

'=====再帰呼び出しを含む実行部分======
'Filesコレクションの操作
For Each FilesList1 In fso1.GetFolder(SearchFolderPath2).Files

    FileName1 = fso1.GetFile(FilesList1).Name
    FilePath1 = fso1.GetFile(FilesList1).Path
    FileChangeDate = fso1.GetFile(FilesList1).DateLastModified
    FileExtensionName1 = fso1.GetExtensionName(FilePath1)
    
    'データをシートに記載
    With ThisWorkbook.Sheets("検索結果一覧")
        LastRow1 = .Cells(.Rows.Count, 1).End(xlUp).Row
        LastRow1 = LastRow1 + 1
        
        .Cells(LastRow1, 1).Value = FileName1
        .Cells(LastRow1, 2).Value = FilePath1
        .Cells(LastRow1, 3).Value = FileExtensionName1
        .Cells(LastRow1, 4).Value = FileChangeDate
    End With
Next FilesList1


'SubFoldersコレクションの操作
For Each FoldersList1 In fso1.GetFolder(SearchFolderPath2).SubFolders

    '自己プロシージャを呼び出している部分
    Call FolderSearchSub(fso1.GetFolder(FoldersList1).Path)
    
Next FoldersList1

'=====再帰呼び出しを含む実行部分の終わり======

End If


'階層数をカウントダウン
KaisouNum = KaisouNum - 1

End Sub

前回の家計簿アプリはExcelファイル自体を操作するコードが多かったですが、
今回のファイル検索アプリはファイル操作の部分がメインになってきますので、
ちょっとイメージしづらい部分もあったかもしれません。

ただ、これができるようになると、Excelマクロを使ってできることの幅が大きく広がりますので、
是非とも頑張って使いこなせるようになっていきましょう!

次の記事へ

コメント

タイトルとURLをコピーしました