フォルダ内一覧化

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

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

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

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

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

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

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

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

【本記事の目標】

再帰呼び出しについて知ろう

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



前回のおさらい・今回やりたいこと

前回の状況

前回までで、
 ・検索したいフォルダのリンクを貼り付け
 ・FileSystemObjectを使ってそのフォルダの中を確認
 ・フォルダの直下にあるファイルの名前等の情報をExcelシートに記載
というところまでできるようにしました。

今回はフォルダの直下にあるフォルダを調べていきます。

 



今回やりたいこと

フォルダの場合、ファイルの時と何が違うの?

と思われるかもしれませんが、
ファイルの場合はその情報を取ってくれば良いですが、
フォルダの場合はその中にもフォルダやファイルが入っている可能性があります。

その中まで調べなくていいから、フォルダの名前とかが分かればいいよ~
であれば確かにファイルの場合と同じになりますが、
それだとマクロなんか使わずに検索したいフォルダを開けばいいですよね。

なので、今回は、
検索したいフォルダの中にあるフォルダを、
一番深くまで潜って隅々まで調べる
というのをやっていきたいのです。



フォルダ階層の深掘り

強引な方法

じゃあ、フォルダの中にあるフォルダを深くまで潜っていこうと思えばどうしたらいいのか。

これは簡単に言えば、
検索フォルダの直下にあるフォルダを、
新しく検索したいフォルダと見なして同じことを繰り返す

ということをすればいいのです。

言葉で書くと分かりにくいので図に書くと、

こんな感じで、検索ファイルの直下を調べている部分のコードを、
そのままフォルダの中を調べる繰り返しの中に入れてしまえば、
検索フォルダの直下にあるフォルダの中を調べていくことができるのです。

ただ、これだと検索フォルダの1階層下のフォルダまでしか調べられないので、
次の階層でも同じようにして、
その次の階層でも同じようにしてぐ、、、
というように繰り返していけばいいのです。

仮に4階層ぐらいを調べようと思うと、下記の感じになります。
(縦に長くなりすぎるので縮小しています。
 字が見にくいですが、イメージだけつかんでもらえたら)

こんな感じに繰り返していけば、一応フォルダ階層の深くまで調べることができます

ただ、4階層までを調べるだけでこれだけ長いコードになってしまうので、
例えば10階層ぐらいを調べようと思うとこの倍以上の長さのコードになるので、
たとえ問題なく動くとしても、
パッと見て「ちょっと強引じゃないかな~」という感じがしませんか?


また、上記は4階層で書きましたが、4階層以上のフォルダになっていると、
4階層より深い部分は上記のコードだと調べることができません

じゃあ10階層分で書いたら?
10階層分書いたとしても、10階層より深い部分は調べることができません。
(10階層もあるフォルダはなかなかないかもしれませんが。。。)

なので、このやり方でどれだけ深い階層のフォルダが来ても大丈夫!としようと思うと、
ありえない階層分まで繰り返し書く必要が出てきてしまいます。

もちろんプログラミングの本質としては、プログラムやマクロが問題なく動くのであれば、
コードの書き方や動きの過程はどうなっていても基本的に問題はないです。
ですが、できる限りシンプルにかける方法を知っていた方が、
後々の修正がやりやすいですし効率も上がってきます


じゃあこの長~~~いコードをどうやったらシンプルにできるのか。
それが再帰呼び出しという方法です。



再帰呼び出しについて

再帰呼び出しとは?

再帰呼び出しというのは、
 For文やIf文のようなコードではなく、
 プロシージャやモジュールといったあるものの名前ではなく、
コードを書く時の書き方のテクニックの1つです。

端的に言うと
 プロシージャの中で自己のプロシージャを呼び出す方法
のことです。

いや、それだけじゃ分からんよ。
となると思いますので、もう少しわかりやすく解説します。

 



プロシージャとは?

そもそも、改めてプロシージャって何?について確認しましょう。

プロシージャについては下記記事でも簡単に説明していますが、

プロシージャを呼び出したときに、
プロシージャの頭から終わり(Sub~End Sub)までプログラムを実行したらそれで終了する、
というプログラム実行の1区切りになるものです。

ユーザー側からプログラムを起動する場合、基本的に1つのプロシージャしか選択できず、
選択したプロシージャを呼び出して、それが終わるとプログラムの起動は終了します。

ユーザー側からは1つのプロシージャしか実行の指示をだせませんが、
プロシージャの中から別のプロシージャを呼び出すこともできます

ここでメインとなるプロシージャAについては1度しか呼び出されませんが、
プロシージャB・Cについては、呼び出しを記載してあれば、
 ・いつでも
 ・好きな時に
 ・何度でも
呼び出すことが可能です。

なので、
 ●毎回必ず実行しないといけない事
 ●少し複雑だけど、同じ内容が何度も出てくる部分
 ●自作の関数として作ったもの
といったものについては、メインとなるプロシージャの中に書いてしまうのではなく、
別のプロシージャとして書いておいて、メインのプロシージャから呼び出した方が、
よりスッキリとわかりやすく、また修正もしやすいコードになります。

 



プロシージャの自己呼び出しとは?

上記のプロシージャB・Cについて、『いつでも』『好きな時に』『何度でも』というのが非常に重要で、メインとなるプロシージャAのどこでも好きな場所で好きなだけ呼び出して良い、
というだけではないです。

プロシージャBの中からプロシージャCを呼び出すことももちろん問題ないですし、
プロシージャBの中からプロシージャBを呼び出すことも問題ないのです。

これがプロシージャの自己呼び出し、いわゆる再起呼び出しの状態なのです。

再起呼び出しの注意点

上記のイメージ図を見ていただければ想像がつくかと思いますが、
プロシージャBを呼び出したら中にプロシージャBを呼び出す部分があって、
さらにそのプロシージャの中にもプロシージャを呼び出す部分があって・・・
という感じで、そのまま書いてしまうと無限ループになってしまい、
プログラムが終わらなくなってしまうエラーになります。

なので、再帰呼び出しを使う場合は、
 自己プロシージャを呼び出す際は必ず条件式の中に入れる
というのが鉄則になります。
(条件式というのは、If文のように、ある条件を満たすときだけ実行するような分岐点です)
もちろんその条件式は、必ずどこかのタイミングで終わるような条件にしておく必要があります。

条件式の中に入れておけば、何回か再帰呼び出しを繰り返した後、条件式に外れる条件になったときに自動的に再起呼び出しが止まり、プログラムも無事に終了できます。

再起呼び出し
 ■意味:【プロシージャ内で自己プロシージャを呼び出す方法】
 ■必要なこと
   ① 再起呼び出しをするプロシージャをメインプロシージャと別に作る
   ② 自己プロシージャを呼び出す場合は、必ず条件式の中に入れる

再帰呼び出しを実行した場合、プロシージャ内で定義した変数の内容はどうなるのか?
と気にされる方もおられるかもしれません。
(初心者の方でそこに気づける方は、非常にセンスがあります!)

結論を言ってしまうと、
 プロシージャ内で定義した変数は基本的に
 プロシージャ内でのみ使用可能、かつデータが保持されます。
 また再帰呼び出しで呼び出したプロシージャは別プロシージャと認識されます。

なので、再帰呼び出しで自己プロシージャを呼び出したとしても、呼び出す前に定義した変数と、呼び出した後に定義した変数は別変数として認識され、それぞれ別々にデータが記録されます。

ただし、これは Dim もしくは Private で変数を定義した場合に限ります。
Public で変数を定義した場合は上記とは異なります。
Public での変数の定義の方法や意味については次回以降で別途記載しようと思います。



マクロ構築

再帰呼び出しするプロシージャ作成

ここまでは「再帰呼び出しとは」について長々と説明してきました。

じゃあ実際に今回のコードで再帰呼び出しをしたらどうなるのか、について書いていこうと思います。

まずは再帰呼び出しをする部分をメインとは別のプロシージャとして作る必要があります。
メインとは別のプロシージャを作って呼び出す方法は、下記の家計簿アプリ作成の際にも説明していますので、ご参考に。

今回は、再帰呼び出しをするプロシージャは別モジュールに記載していきます。
モジュール名やプロシージャ名は何でもいいですが、今回は
 モジュール名:SaikiYobidashi
 プロシージャ名:FolderSearchSub
にしました。

ここに書くコードですが、上記に4層目まで繰り返し書いたコードがあったと思いますが、
あれの1層分をそのまま書くだけで問題ないです。
ただし、変数の定義については別途必要なので、その部分を追記してあげる必要があります。
またFileSystemObjectについても定義してあげる必要があります。

なので、今回の場合は実はほぼメインプロシージャに書いていた内容がほぼそのままですね。

実際に書くと下記のようになります。

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


'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 Sub

これだけ見ると、
 「あれ?メインプロシージャに書いていた内容と何が違うの?」
となるかもしれません。
ほとんど間違い探しレベルです。。。

変わっているのは下記部分でした。

再帰呼び出しでプロシージャを呼び出す際に、どのフォルダを検索したいのか、を与えてあげた方がマクロが作りやすくなりますので、フォルダのパスは引数として与える形にしています。

なので、もともとメインプロシージャで書いていたフォルダパスを入れる変数の定義部分が不要になってます。

あとは、フォルダの中にあるフォルダを調べていく部分。
ここで今回再帰呼び出しをしていくので、自己プロシージャを呼び出すようにしています。
この時、調べたいフォルダのパスを引数として与えてあげる必要があるので、
 fso1.GetFolder(FolderList1).Path
の部分でフォルダの中にある1つのフォルダのパスを与えています。
この部分の書き方は、ファイルのパスを書くときに使っているので、
イメージはしやすいかと思います。
Fileと書いてあった部分が全てFolderに代わっているのが注意点ですが。

引数とは?

引数というのは、そのプロシージャを起動させるために事前に与えてあげる必要がある値のことです。
なので、その値を与えてあげないとプロシージャが動き出すことはできません。

例えば足し算をイメージしてもらうと、
『+』という記号があったところで、「だから何?」となりますよね。
ここに、『3+4』という形で両サイドに数字を書いてあげたら、「あー答えは7だね。」と足し算ができるようになります。
ここで言う『+』という記号がプロシージャ
『3』『4』という数値が引数のことになります。

プロシージャに引数を定義する場合は、下記のように記載します。
 Sub プロシージャ名(引数名1 as データ形式, 引数名2 as データ形式,・・・)
引数は1つでも複数でも可能です。
この形でプロシージャを作ってあれば、このプロシージャを呼び出すときに、
 Call プロシージャ名(値1, 値2,・・・)
という形で設定した引数の数だけ値を与えてあげれば、プロシージャが動き出します。

(※ ちなみに、上記の足し算の例でいくと、
   『+』という記号がプロシージャ、『3』『4』という数値が引数ですが、
   出てくる答えの『7』は戻り値という言い方をします。)

 



メインプロシージャに再帰呼び出しを記載

これで再帰呼び出しするプロシージャができました。
あとはこれをメインプロシージャで呼び出してあげればOKです。

ただ、再帰呼び出しするプロシージャの方で主要な部分は書いてしまっているので、
かぶっている部分は消してあげる必要があります。
上記に書いたように、ほとんどが同じなので、実際に残る部分はごくわずかになります。

実際にメインプロシージャを修正すると下記のようになります。

Option Explicit

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

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

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

End Sub

たったこれだけ?
と思われるかもしれませんが、たったこれだけなのです。

このメインプロシージャと、先ほど書いた再帰呼び出しするプロシージャの2つがあれば、
検索したいフォルダの中にどれだけ階層があったとしても、
全てのファイルを検索し終わるまで検索し続けてくれるようになります。

上記で強引な方法で書いていたコードと比べると、かなりあっさりしますよね。
これが再帰呼び出しを使ったテクニックによる効果です。

 



完成形

今回作ったメインプロシージャと再帰呼び出しするプロシージャを2つ並べると
下記のようになります。

実際に、シートにボタンを作ってメインプロシージャを登録してマクロを起動させてみてください。

検索フォルダの欄に検索したいフォルダのフルパスが記入されてあれば、
そのフォルダの中にある全てのファイルのデータが下の表に記載されているはずです。

試しに下記フォルダ構成のフォルダを検索した結果を置いておきます。

お試し検索フォルダのフォルダ構成
検索結果(抜粋)

このように、4階層ぐらい深い部分にある『お宝.txt』というファイルをしっかりと見つけられていると思います。


これでほぼ完成でOKですが、このままだと階層があまりにありすぎるフォルダを検索したとき、
検索に時間がかかりすぎてマクロが終わるのにすごく時間がかかってしまうときがあります。
なので、
 ・5階層ぐらいまで検索出来たらいいや~
 ・いや今回はちょっと深く10階層ぐらいは検索しておきたいな~
という感じで、検索する階層を任意で変更できるようにしておいた方が、より汎用性が上がるかと思います。

なので次回は、その機能を搭載していこうと思います。

次の記事へ

コメント

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