【Excel VBA入門 第21回】フォルダ内のファイルを一覧にしよう!Dir関数とFileDialogの使い方

スポンサーリンク
Excel VBA

イントロダクション

前回(第20回)は、ブックを開く・保存する・閉じるという、ブックの基本操作を取り上げた。
ただし、開くブックのファイル名を、コードに直接書いていた。

ところで、実務では、「このフォルダにある月別のブックを、すべて処理したい」という場面が多い。そのためには、フォルダの中にどんなファイルがあるのかを、マクロで調べる必要がある。

今回は、そのための2つの道具を取り上げよう。

  • Dir 関数:フォルダ内のファイル名を、順番に取り出す
  • FileDialog:ダイアログを表示して、フォルダやファイルを選んでもらう

次回(第22回)の集計マクロに向けた、準備の回となる。

準備:サンプルのフォルダとファイルを用意する

今回は、次のフォルダとファイルを準備しよう。

  • C:\test\月別売上 というフォルダを作成しよう
  • Excelで新しいブックを3つ作り、それぞれ次のように入力して、フォルダの中に保存しよう
ファイル名A1セルB1セル
売上_1月.xlsx1月100
売上_2月.xlsx2月200
売上_3月.xlsx3月300
  • メモ帳で、適当な内容でテキストファイル メモ.txt を作り、同じフォルダに保存しよう

メモ.txt は、「Excel以外のファイルが混ざっている」場面を再現するためのものである。
なお、サンプルのブックは、書き込みが終わったら閉じておこう。

Dir関数の基本

Dir 関数は、指定した条件に合うファイル名を、1件ずつ返す関数である。

Dir関数
Dir(パス) → 最初に見つかったファイル名を返す(見つからなければ空の文字列 "" )
Dir → 次のファイル名を返す(引数を省略して呼び出す)

第19回で使った If Dir(パス) = "" Then という存在確認は、「最初の1件を返す」という、この動作を利用したものである。まずは、最初の1件だけ取り出してみよう。

Sub 最初のファイル名を調べる()
    Dim ファイル名 As String

    ファイル名 = Dir("C:\test\月別売上\*.xlsx")
    MsgBox "最初のファイル:" & ファイル名
End Sub

*.xlsx の * は、「任意の文字列」を表すワイルドカードである。つまり、「拡張子が .xlsx のファイル」という条件になる。どのファイルが最初に返るかは、フォルダの中の並び順で決まる。

ここで、2点確認しておこう。

  • Dir が返すのは、ファイル名だけである。フォルダの場所(C:\test\月別売上\)は含まれない。ブックを開くときなどは、フォルダ & ファイル名 とつなげる必要がある
  • Dir で調べられるのは、指定したフォルダの直下だけである。中にあるサブフォルダの、さらに中のファイルは対象にならない

すべてのファイル名を取り出す

2件目以降は、引数なしの Dir を呼び出すと取り出せる。見つかるファイルがなくなると "" が返ってくるので、それを繰り返しの終了条件にする。

【注意】
このマクロの出力はDebug.Printなので、画面には出ず、VBEの「イミディエイトウィンドウ」に表示される。Ctrl + Gで開こう。なお、VBEのメニュー[表示]→[イミディエイト ウィンドウ]でも開ける。

Sub ファイル名を一覧にする()
    Const フォルダ As String = "C:\test\月別売上\"
    Dim ファイル名 As String

    ファイル名 = Dir(フォルダ & "*.xlsx")
    Do While ファイル名 <> ""
        Debug.Print ファイル名
        ファイル名 = Dir
    Loop
End Sub

流れは次のとおりである。

  1. 最初に Dir(フォルダ & "*.xlsx") で、1件目を取り出す
  2. ファイル名 が "" でない間、処理を繰り返す
  3. 繰り返しの最後に、引数なしの Dir で、次のファイル名を取り出す

実行して、イミディエイトウィンドウ(Ctrl + G)を見てみよう。売上_1月.xlsx、売上_2月.xlsx、売上_3月.xlsx の3つが表示されるはずである。メモ.txt は条件に合わないので、表示されない。なお、表示される順番は、環境によって異なることがある。

ワイルドカードで条件を指定する

Dir の条件には、次の2種類のワイルドカードが使える。

  • *:任意の長さの文字列(0文字以上)
  • ?:任意の1文字
条件意味見つかるファイルの例
*.xlsx拡張子が .xlsx のファイル売上_1月.xlsx、売上_2月.xlsx、売上_3月.xlsx
売上_*.xlsx売上_ で始まる .xlsx同上
売上_?月.xlsx売上_ + 1文字 + 月.xlsx同上(売上_10月.xlsx は、? が1文字なので対象外)
*.*すべてのファイルメモ.txt を含む、すべて

ひとつ注意がある。*.xls と指定すると、.xlsx のファイルまで見つかってしまうことがある
。古い形式の .xls だけを探したいときは、拡張子の確認を、別に行う。

一覧をシートに書き出す

一覧をイミディエイトウィンドウに表示するだけでなく、シートに書き出すこともできる。

Sub ファイル一覧をシートに書き出す()
    Const フォルダ As String = "C:\test\月別売上\"
    Dim ws As Worksheet
    Dim ファイル名 As String
    Dim 行 As Long

    Set ws = ThisWorkbook.Worksheets.Add
    ws.Range("A1").Value = "ファイル名"
    行 = 2

    ファイル名 = Dir(フォルダ & "*.xlsx")
    Do While ファイル名 <> ""
        ws.Cells(行, 1).Value = ファイル名
        行 = 行 + 1
        ファイル名 = Dir
    Loop
End Sub

新しいシートを作る Worksheets.Add は、第5回で学んだ方法である。今回は ThisWorkbook.Worksheets.Add と書いているので、マクロ有効ブックの中にシートが追加される。位置を指定していないため、実行したときに表示していたシートのすぐ左側に、Sheet2 のような名前で追加され、そのシートが画面に表示される。
なお、実行するたびに新しいシートが追加されるので、手動で削除しておこう。

一時ファイルを除外する

Excelでブックを開いていると、同じフォルダに ~$売上_1月.xlsx のような名前の一時ファイル(ロックファイル)が作られる。通常は隠しファイルなので、Dir の結果には出てこないが、条件によって一覧に混ざる可能性がある。そこで、~$ で始まるファイル名は処理の対象から外す書き方を、習慣にしておくと安心である。

Sub 一時ファイルを除いて一覧にする()
    Const フォルダ As String = "C:\test\月別売上\"
    Dim ファイル名 As String

    ファイル名 = Dir(フォルダ & "*.xlsx")
    Do While ファイル名 <> ""
        If Left(ファイル名, 2) <> "~$" Then
            Debug.Print ファイル名
        End If
        ファイル名 = Dir
    Loop
End Sub

【注意】実行しても、シートやメッセージボックスには何も表示されない。
結果は、VBEの「イミディエイトウィンドウ」(Ctrl + Gで開く)に出力される。サンプルのフォルダ(売上_1月.xlsx、売上_2月.xlsx、売上_3月.xlsx、メモ.txt)なら、次のように1行ずつ表示される。

売上_1月.xlsx
売上_2月.xlsx
売上_3月.xlsx

Left(ファイル名, 2) は、文字列の先頭から2文字を取り出す関数である。

【注意!】繰り返しの途中で、別のDirを使ってはいけない

Dir には、とても大切な注意点がある。

Dir は、「今どこまで探したか」を1つだけ覚えている。そのため、一覧を取り出している繰り返しの途中で、別の Dir(パス) を実行すると、それまでの探索の続きが失われる。

' ==========================================
' 【NG例】繰り返しの途中で Dir を使う関数を呼んでいる場合
' ==========================================
Do While ファイル名 <> ""
    ' 中で Dir(パス) を使っているため、大元のループの検索状態が破壊される
    Set wb = ブックを安全に開く(フォルダ & ファイル名)
    
    ' (ここに何らかの処理が入る)
    
    ' 期待した「1件目の続き」ではなく、別の探索の続きになってしまい無限ループやバグの原因に
    ファイル名 = Dir
Loop

第20回で作った ブックを安全に開く は、中で Dir(パス) を使って存在確認をしている。これを繰り返しの中で呼ぶと、一覧が途中で終わってしまい、一部のファイルが処理されなくなる。

対策は、先にファイル名をすべて取り出して、どこかに保存しておき、その後で1件ずつ処理することである。取り出した後なら、Dir を自由に使える。

ファイル名をまとめて取得するFunction

ファイル名の保存先には、Collection(コレクション)を使うのが便利である。第1回で学んだ Workbooks や Worksheets と同じ「オブジェクトの集まり」を、自分で作れるものだと考えるとよい。Add で要素を追加でき、Count で件数が分かる。

先ほどの内容を、第12回で学んだ Function にまとめてみよう。

Function ファイル一覧を取得する(フォルダ As String, パターン As String) As Collection
    Dim 一覧 As New Collection
    Dim ファイル名 As String

    ファイル名 = Dir(フォルダ & パターン)
    Do While ファイル名 <> ""
        If Left(ファイル名, 2) <> "~$" Then
            一覧.Add ファイル名
        End If
        ファイル名 = Dir
    Loop

    Set ファイル一覧を取得する = 一覧
End Function

使うときは、次のようにする。
For Each で取り出す変数は、Collection の場合は Variant で宣言する。

Sub 一覧を取得して表示する()
    Dim 一覧 As Collection
    Dim ファイル名 As Variant

    Set 一覧 = ファイル一覧を取得する("C:\test\月別売上\", "*.xlsx")
    MsgBox 一覧.Count & " 件のブックが見つかりました。"

    For Each ファイル名 In 一覧
        Debug.Print ファイル名
    Next ファイル名
End Sub

メッセージボックス に「3 件のブックが見つかりました。」と表示され、イミディエイトウィンドウに3つのファイル名が表示されれば成功である。この方法なら、For Each の中で ブックを安全に開く のような、Dir を使うプロシージャを呼んでも問題ない。

【注意】
これまでにマクロを何度も実行していると、イミディエイトウィンドウの表示が乱れる可能性がある。実行前にイミディエイトウィンドウの中をCtrl + A → Deleteで消しておくとよい。

FileDialogでフォルダを選んでもらう

ここまでは、フォルダの場所を、コードに直接書いていた。しかし、他の人が使うマクロでは、フォルダを自分で選べると便利である。そのために使うのが、FileDialog である。

フォルダを選択するダイアログ
Application.FileDialog(msoFileDialogFolderPicker)

Function フォルダを選ぶ() As String
    With Application.FileDialog(msoFileDialogFolderPicker)
        .Title = "フォルダを選択してください"
        If .Show = -1 Then
            フォルダを選ぶ = .SelectedItems(1)
        Else
            フォルダを選ぶ = ""
        End If
    End With
End Function
  • .Title:ダイアログのタイトルを指定する
  • .Show:ダイアログを表示する。[OK]で閉じると -1、[キャンセル]で閉じると 0 が返る
  • .SelectedItems(1):選択されたフォルダのパスを取り出す

キャンセルされたときは、空の文字列 "" を返す設計にしている。呼び出す側で、これを確認すれば、キャンセルにも対応できる。

以下のマクロを実行してみよう。

Sub 選んだフォルダのブックを一覧にする()
    Dim フォルダ As String
    Dim 一覧 As Collection
    Dim ファイル名 As Variant

    フォルダ = フォルダを選ぶ()
    If フォルダ = "" Then
        MsgBox "キャンセルされました。"
        Exit Sub
    End If

    ' 末尾に「\」がなければ付ける
    If Right(フォルダ, 1) <> "\" Then フォルダ = フォルダ & "\"

    Set 一覧 = ファイル一覧を取得する(フォルダ, "*.xlsx")
    MsgBox 一覧.Count & " 件のブックが見つかりました。"

    For Each ファイル名 In 一覧
        Debug.Print ファイル名
    Next ファイル名
End Sub

ダイアログで選んだフォルダのパスは、末尾に \ が付かないことが多い。そのまま ファイル名 とつなげると、C:\test\月別売上売上_1月.xlsx のような間違ったパスになってしまう。そこで、Right(フォルダ, 1) で最後の1文字を確認し、\ でなければ付け足している。

実行すると、次の順に動く。

1.フォルダ選択のウインドウ開く:タイトルが「フォルダを選択してください」のエクスプローラーに似たウインドウが表示される(フォルダを選ぶFunctionの動作である)。
C:\test\月別売上を選んで[OK]を押そう。

2.キャンセルした場合:「キャンセルされました。」というメッセージが出て、マクロが終わる。
3.[OK]を押した場合:選んだフォルダのパスの末尾に\がなければ付け足す。
4.ブックを検索する:ファイル一覧を取得するが、そのフォルダ直下の.xlsxファイルを探す。(~$で始まる一時ファイルは除外される)
5.見つかったブックの件数がメッセージで表示される:サンプルのフォルダなら「3 件のブックが見つかりました。」と表示される。
6.ファイル名が出力される:[OK]を押すと、売上_1月.xlsx、売上_2月.xlsx、売上_3月.xlsxが、イミディエイトウィンドウに1行ずつ表示される。

選んだフォルダに.xlsxが1つもなければ、「0 件のブックが見つかりました。」と表示され、イミディエイトウィンドウには何も出ない。たとえばC:\testを選ぶと、第20回で作った売上.xlsxなどが対象になる。また、中にあるサブフォルダの.xlsxは、一覧に含まない。

なお、このマクロを動かすには、フォルダを選ぶとファイル一覧を取得するの2つのFunctionが、同じ標準モジュールに入っている必要がある。どちらかが欠けていると、「Sub または Function が定義されていません」というコンパイルエラーになる。

ファイルを1つ選んでもらう

フォルダではなく、ファイルを選んでもらいたいときは、msoFileDialogFilePicker を使う。

Sub ファイルを選ぶ()
    With Application.FileDialog(msoFileDialogFilePicker)
        .AllowMultiSelect = False
        .Filters.Clear
        .Filters.Add "Excelブック", "*.xlsx"

        If .Show = -1 Then
            MsgBox "選んだファイル:" & .SelectedItems(1)
        Else
            MsgBox "キャンセルされました。"
        End If
    End With
End Sub
  • .AllowMultiSelect:True にすると、複数のファイルを選べる(今回は False)
  • .Filters.Add:選べるファイルの種類を、絞り込む

実行すると、次の順に動く。

1.ファイル選択のウィンドウが開く:フォルダ選択と同じように、エクスプローラーに似たウィンドウが表示される(参照)。今回は、フォルダではなくファイルを選ぶウィンドウである。

2.表示されるファイルが絞り込まれる:.Filters.Clearで標準の絞り込みを消し、.Filters.Add "Excelブック", "*.xlsx"で「Excelブック」という種類(*.xlsx)だけを追加していく。そのため、.xlsxのファイルだけが一覧に表示され、メモ.txtなどは表示されない。

3.選べるのは1つだけ:.AllowMultiSelect = Falseなので、複数のファイルは同時に選べない。

4.選んで決定した場合:C:\test\月別売上\売上_1月.xlsxのようなフルパスが、「選んだファイル:…」というメッセージで表示される。

5.キャンセルした場合:「キャンセルされました。」と表示される。

【注意】
決定のボタンは「開く」と表示されるが、ブックが開くわけではない。選んだファイルのパスが、取得されるだけである。
.xlsmや.xlsのファイルも表示したいときは、拡張子をセミコロンでつなげて、
.Filters.Add “Excelブック”, “*.xlsx;*.xlsm;*.xls”
ように指定する。

まとめ

今回は、フォルダ内のファイルを調べる方法を学んだ。

  • Dir(パス) で最初のファイル名を、引数なしの Dir で次のファイル名を取り出せる。
  • 戻り値はファイル名だけで、見つからなくなると "" が返る。
  • * と ? のワイルドカードで、条件を指定できる。
  • ~$ で始まる一時ファイルは、除外しておくと安全である。
  • 繰り返しの途中で、別の Dir(パス) を使うと、一覧が壊れる。先にファイル名をすべて取り出し、Collection などに保存してから処理する。
  • FileDialog を使えば、フォルダやファイルを選んでもらえる。キャンセルへの対応も忘れない。

次回(第22回)は、ブック・ファイル操作編の最終回として、これまでの内容をまとめた実践編に挑戦する。フォルダ内のブックを順番に開き、決まったセルの値を、集計用のシートに転記するマクロを作ろう。第12回のFunctionやエラー処理編の後始末も、ここで活躍する。

コメント

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