Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2010年01月25日] [更新 2019年02月26日] Delphi サンプルプログラム集
817_ディレクトリとファイルのリストとファイル検索
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
817_DirFileListTest.zip [7,955 KB] 2019年02月26日版 (EXE 同梱)







Delphi 2010 以降のディレクトリ ( フォルダ ) とファイル操作関数


Delphi 2010 ではディレクトリ (フォルダ) とファイル関係の便利な関数が追加されています。
これらの関数は IOUtils.pas 内に収められている以下のレコード型として定義されています。これらを使用すると、サンプルプログラム集で紹介しているコードと同じ動作をするプログラム類が比較的簡単に作成することができます。
以下のサンプルコードは、それらの一部の機能だけですが、ディレクトリの一覧、または、ファイルの一覧を取得するテストプログラムです。比較のために、Delphi 2010 より前のバージョンの Delphi で同じような動作をするテストプログラムも掲載しています。

表1 

IOUtils.TDirectory ディレクトリのコピー、移動、作成、名前変更、削除、存在確認 等
ディレクトリの属性およびタイムスタンプ (日付や時刻) の操作
ディレクトリの再帰的な検索 ([備考] を参照)
IOUtils.TFile ファイルのコピー、移動、作成、名前変更、削除、存在確認 等
ファイルの属性やタイムスタンプ (日付や時刻) の操作
ファイル内容へのアクセス
IOUtils.TPath ファイルのパスまたはファイル名の正当性の確認 等
パス文字列の部分の抽出
テンポラリなパスの生成や一意なパスの生成


これらで用意されている関数 (メソッド) 類は .NET Framework の クラスメソッドと互換性のある記述で使用します。ディレクトリやファイルのリストのメソッドの戻り値は、Type.pas で定義されている TStringDynArray という文字列型の動的配列です。
この型の動的配列には、TIntegerDynArray, TByteDynArray, TDoubleDynArray, TBooleanDynArray や TCardinalDynArray 等があります。

  TStringDynArray = array of string;


[備考]
「ディレクトリの再帰的な検索」というのは、対象のディレクトリ内に別のディレクトリ (サブディレクトリ) が存在する場合、そのサブディレクトリ内も検索するという意味です。
そのサブディレクトリの階層下に、更に別のディレクトリが存在する場合、そのディレクトリ内も検索します。「再帰」というのは、再び処理を戻してくるという意味です。
ディレクトリの検索の場合は、サブディレクトリが見つからなくなると、その上の階層に再び戻って (帰って)、更に別のディレクトリを探します。その階層にも見つからなければ、更に上に階層に処理を戻して再び検索します。



ファイルやディレクトリのコピー、削除、移動には以下のような関数類も使用できます。
RenameFile 関数は、新しいファイル名のディレクトリ部分が元のディレクトリ名と異なっている場合、ファイルの移動の操作と同じ結果になります。RenameFile 関数はディレクトリ名の変更にも利用できます。





01_Delphi 2010 以降専用のコード


Delphi 2010 以降専用のサンプルです。指定したディレクトリ直下のディレクトリのみ、またはファイルを取得して ListBox に表示します。
TDirectory の GetDirectories と GetFiles メソッドを使用しています。これらのメソッドの引数を変更することにより、サブディレクトリ内のリストも取得可能です。また検索パターンの指定もできます。
このサンプルでは ListBox の Style プロパティの値を lbOwnerDrawFixed にして、ItemHeight の値を 15 に変更しています。


図1
設計時画面
図2
ディレクトリのリストの取得結果例
図3
ファイルリストの取得結果例

リスト1
TDirectory.GetDirectories と TDirectory.GetFiles を使用したサンプル
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, ComCtrls;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    ListBox1: TListBox;
    Button1: TButton;
    Button2: TButton;
    StatusBar1: TStatusBar;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses 
  FileCtrl, 
  Types, 
  IOUtils;

{$R *.dfm}

//=============================================================================
//  ディレクトリのリスト
//  GetDirectoryの引数を追加するとでディレクトリ名に検索条件を付けたり、サブデ
//  ィレクトリ内の検索も可能
//  TPath.GetFileNameはディレクトリ名のフルパスから最後のデリミタ以降を取出す
//  これがなければフルパスとなる
//
//  SelectDirectoryの使用にはusesにFileCtrlが必要
//  TDirectory,TPathの使用にはusesにIOUtilsが必要
//  TStringDynArrayの使用にはusesにTypesが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LDir     : String;
  LDirList : TStringDynArray;
  LDirName : String;
  i        : Integer;
begin
  StatusBar1.Panels[0].Text := '';
  StatusBar1.Update;

  if FileCtrl.SelectDirectory('検索フォルダ指定', '', LDir) then begin
    ListBox1.Items.Clear;
    ListBox1.Items.BeginUpdate;

    //APath内のディレクトリのリスト作成
    //[マイコンピュータ]等の特殊フォルダは指定しても無効
    LDirList := TDirectory.GetDirectories(LDir);
    for LDirName in LDirList do begin
      ListBox1.Items.Add(TPath.GetFileName(LDirName));
    end;
    ListBox1.Items.EndUpdate;

    //検索結果を表示
    StatusBar1.Panels[0].Text := ' ディレクトリ数 : ' + IntToStr(Length(LDirList));
  end;
end;

//=============================================================================
//  指定ディレクトリ内のファイルのリスト
//  GetFilesの引数を追加するとでディレクトリ名に検索条件を付けたり、サブデ
//  ィレクトリ内の検索も可能
//  TPath.GetFileNameはディレクトリ名のフルパスから最後のデリミタ以降を取出す
//  これがなければフルパスとなる
//
//  SelectDirectoryの使用にはusesにFileCtrlが必要
//  TDirectory,TPathの使用にはusesにIOUtilsが必要
//  TStringDynArrayの使用にはusesにTypesが必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LDir       : String;
  LFilesList : TStringDynArray;
  LFileName  : String;
  i          : Integer;
begin
  if FileCtrl.SelectDirectory('検索フォルダ指定', '', LDir) then begin
    ListBox1.Items.Clear;
    ListBox1.Items.BeginUpdate;

    //APath内のディレクトリ内のファイルリスト作成
    //[マイコンピュータ]等の特殊フォルダは指定しても無効
    LFilesList := TDirectory.GetFiles(LDir);
    for LFileName in LFilesList do begin
      ListBox1.Items.Add(TPath.GetFileName(LFileName));
    end;
    ListBox1.Items.EndUpdate;

    //検索結果を表示
    StatusBar1.Panels[0].Text := ' ファイル数 : ' + IntToStr(Length(LFilesList));
  end;
end;

end.




02_FindFirst と FindNext による方法 - Delphi 2009 以前用


Delphi 2010 よりも前のバージョンで使用可能なサンプルです。
指定したディレクトリ直下のディレクトリのみ、またはファイルを取得して ListBox に表示します。SysUtils.pas 内の FindFirst と FindNex を使用しています。この手のサンプルは、このサンプルプログラム集にもいくつかあります。
このサンプルでは ListBox の Style プロパティ の値を lbOwnerDrawFixed にして、ItemHeight の値を 15 に変更しています。

FindFirst 関数によるファイルの検索ではワイルドカードが使用できます。



図4
設計時画面
図5
ディレクトリのリストの取得結果例
図6
ファイルリストの取得結果例

リスト2
FindFirst と FindNext を使用して、ディレクトリとファイルのリストを取得
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, ComCtrls;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    ListBox1: TListBox;
    Button1: TButton;
    Button2: TButton;
    StatusBar1: TStatusBar;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    procedure GetDirectories(ADir: string; AList: TStringList);
    procedure GetFiles(ADir: string; AList: TStringList);
  end;

var
  Form1: TForm1;

implementation

uses
  FileCtrl;

{$R *.dfm}

//=============================================================================
//  ディレクトリのリスト
//
//  SelectDirectoryの使用にはusesにFileCtrlが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LDir     : String;
  LDirList : TStringList;
  i        : Integer;
begin
  StatusBar1.Panels[0].Text := '';
  StatusBar1.Update;

  if FileCtrl.SelectDirectory('検索フォルダ指定', '', LDir) then begin
    ListBox1.Items.Clear;
    ListBox1.Items.BeginUpdate;

    LDirList := TStringList.Create;
    try
      //APath内のディレクトリのリスト作成
      //[マイコンピュータ]等の特殊フォルダを指定した場合は不定となるので注意
      GetDirectories(LDir, LDirList);

      //取得したリストをListBoxに表示
      //ListBox1.Items := LDirList;でも可
      //何か処理を追加する場合はループで回して処理
      for i := 0 to LDirList.Count - 1 do begin
        ListBox1.Items.Add(LDirList[i]);
      end;
      ListBox1.Items.EndUpdate;

      //検索結果を表示
      StatusBar1.Panels[0].Text := ' ディレクトリ数 : ' + IntToStr(LDirList.Count);
    finally
      FreeAndNil(LDirList);
    end;
  end;
end;

//=============================================================================
//  指定ディレクトリ内のファイルのリスト
//
//  SelectDirectoryの使用にはusesにFileCtrlが必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LDir       : String;
  LFilesList : TStringList;
  i          : Integer;
begin
  StatusBar1.Panels[0].Text := '';
  StatusBar1.Update;

  if FileCtrl.SelectDirectory('検索フォルダ指定', '', LDir) then begin
    ListBox1.Items.Clear;
    ListBox1.Items.BeginUpdate;

    LFilesList := TStringList.Create;
    try
      //APath内のディレクトリのファイルリスト作成
      //[マイコンピュータ]等の特殊フォルダを指定した場合は不定となるので注意
      GetFiles(LDir, LFilesList);

      //取得したリストをListBoxに表示
      //ListBox1.Items := LFilesList;でも可
      //何か処理を追加する場合はループで回して処理
      for i := 0 to LFilesList.Count - 1 do begin
        ListBox1.Items.Add(LFilesList[i]);
      end;
      ListBox1.Items.EndUpdate;

      //検索結果を表示
      StatusBar1.Panels[0].Text := ' ファイル数 : ' + IntToStr(LFilesList.Count);
    finally
      FreeAndNil(LFilesList);
    end;
  end;
end;

//=============================================================================
//  指定ディレクトリ内のディレクトリを検索して引数のAListに返す
//  FindFirstとFindNexを使用したサンプル
//  ディレクトリ名とか属性等の検索条件が必要ならば、その処理を追加する
//=============================================================================
procedure TForm1.GetDirectories(ADir: string; AList: TStringList);
var
  LSearchPath : String;
  LSearchRec  : TSearchRec;
begin
  LSearchPath := IncludeTrailingPathDelimiter(ADir) + '*.*';
  if FindFirst(LSearchPath, faAnyFile, LSearchRec) = 0 then begin
    repeat
      if not ((LSearchRec.Name = '..') or (LSearchRec.Name = '.')) then begin
        //ディレクトリだけをリストに追加する
        if (LSearchRec.Attr and faDirectory) = faDirectory then begin
          //IncludeTrailingPathDelimiter(ADir) + LSearchRec.Nameとすればフルパス
          AList.Add(LSearchRec.Name);
        end;
      end;
    until FindNext(LSearchRec) <> 0;
    FindClose(LSearchRec);
  end;
end;

//=============================================================================
//  指定ディレクトリ内のファイルを検索して引数のAListに返す
//  FindFirstとFindNexを使用したサンプル
//  ファイル名とか属性等の検索条件が必要ならば、その処理を追加する
//=============================================================================
procedure TForm1.GetFiles(ADir: string; AList: TStringList);
var
  LSearchPath : String;
  LSearchRec  : TSearchRec;
begin
  LSearchPath := IncludeTrailingPathDelimiter(ADir) + '*.*';
  if FindFirst(LSearchPath, faAnyFile, LSearchRec) = 0 then begin
    repeat
      if not ((LSearchRec.Name = '..') or (LSearchRec.Name = '.')) then begin
        //ディレクトリでないものをリストに追加する
        if (LSearchRec.Attr and faDirectory) <> faDirectory then begin
          //IncludeTrailingPathDelimiter(ADir) + LSearchRec.Nameとすればフルパス
          AList.Add(LSearchRec.Name);
        end;
      end;
    until FindNext(LSearchRec) <> 0;
    FindClose(LSearchRec);
  end;
end;

end.


サブディレクトリ内も検索したいのであれば、次のように自分自身を呼出す、再帰処理のコードを追加します。後述のファイルの検索のサンプルコードも参考にしてください。


リスト3
ディレクトリのリストを取得。サブディレクトリもリストする例
ファイルの場合も同様な考え方で可能
//=============================================================================
//  指定ディレクトリ内のディレクトリを検索して引数のAListに返す
//  FindFirstとFindNexを使用したサンプル
//  サブディレクトリも検索する場合
//
//  ディレクトリ名とか属性等の検索条件が必要ならば、その処理を追加する
//=============================================================================
procedure TForm1.GetDirectories(ADir: string; AList: TStringList);
var
  LSearchPath : string;
  LSearchRec  : TSearchRec;
  LFullPath   : string;
begin
  LSearchPath := IncludeTrailingPathDelimiter(ADir) + '*.*';
  if FindFirst(LSearchPath, faAnyFile, LSearchRec) = 0 then
  begin
    repeat
      if not ((LSearchRec.Name = '..') or (LSearchRec.Name = '.')) then begin
        //ディレクトリだけをリストに追加する
        if (LSearchRec.Attr and faDirectory) = faDirectory then begin
          LFullPath := IncludeTrailingPathDelimiter(ADir) + LSearchRec.Name;
          AList.Add(LFullPath);
          //パス名を与えて自分自身を呼出(再帰処理)する
          GetDirectories(LFullPath, AList);
        end;
      end;
    until FindNext(LSearchRec) <> 0;
    FindClose(LSearchRec);
  end;
end;




03_FindFirstFile と FindNextFile による方法 - Delphi 2009 以前用


Delphi 2010 よりも前のバージョンで使用可能なサンプルです。
指定したディレクトリ直下のディレクトリのみ、またはファイルを取得して ListBox に表示します。Windows API の FindFirstFile と FindNextFile を使用しています。
このサンプルでは ListBox の Styleプロパティ の値を lbOwnerDrawFixed にして、ItemHeight の値を 15 に変更しています。

FindFirstFile 関数によるファイルの検索ではワイルドカードが使用できます。



図7
設計時画面
図8
ディレクトリのリストの取得結果例
図9
ファイルリストの取得結果例

リスト4
FindFirstFile と FindNextFile を使用して、ディレクトリとファイルのリストを取得
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, ComCtrls;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    ListBox1: TListBox;
    Button1: TButton;
    Button2: TButton;
    StatusBar1: TStatusBar;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    procedure GetDirectories(ADir: string; AList: TStringList);
    procedure GetFiles(ADir: string; AList: TStringList);
  end;

var
  Form1: TForm1;

implementation

uses
  FileCtrl;

{$R *.dfm}

//=============================================================================
//  ディレクトリのリスト
//
//  SelectDirectoryの使用にはusesにFileCtrlが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LDir     : String;
  LDirList : TStringList;
  i        : Integer;
begin
  StatusBar1.Panels[0].Text := '';
  StatusBar1.Update;

  if FileCtrl.SelectDirectory('検索フォルダ指定', '', LDir) then begin
    ListBox1.Items.Clear;
    ListBox1.Items.BeginUpdate;

    LDirList := TStringList.Create;
    try
      //APath内のディレクトリのリスト作成
      //[マイコンピュータ]等の特殊フォルダを指定した場合は不定となるので注意
      GetDirectories(LDir, LDirList);

      //取得したリストをListBoxに表示
      //ListBox1.Items := LDirList;でも可
      //何か処理を追加する場合はループで回して処理
      for i := 0 to LDirList.Count - 1 do begin
        ListBox1.Items.Add(LDirList[i]);
      end;
      ListBox1.Items.EndUpdate;

      //検索結果を表示
      StatusBar1.Panels[0].Text := ' ディレクトリ数 : ' + IntToStr(LDirList.Count);
    finally
      FreeAndNil(LDirList);
    end;
  end;
end;

//=============================================================================
//  指定ディレクトリ内のファイルのリスト
//
//  SelectDirectoryの使用にはusesにFileCtrlが必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LDir       : String;
  LFilesList : TStringList;
  i          : Integer;
begin
  StatusBar1.Panels[0].Text := '';
  StatusBar1.Update;

  if FileCtrl.SelectDirectory('検索フォルダ指定', '', LDir) then begin
    ListBox1.Items.Clear;
    ListBox1.Items.BeginUpdate;

    LFilesList := TStringList.Create;
    try
      //APath内のディレクトリのファイルリスト作成
      //[マイコンピュータ]等の特殊フォルダを指定した場合は不定となるので注意
      GetFiles(LDir, LFilesList);

      //取得したリストをListBoxに表示
      //ListBox1.Items := LFilesList;でも可
      //何か処理を追加する場合はループで回して処理
      for i := 0 to LFilesList.Count - 1 do begin
        ListBox1.Items.Add(LFilesList[i]);
      end;
      ListBox1.Items.EndUpdate;

      //検索結果を表示
      StatusBar1.Panels[0].Text := ' ファイル数 : ' + IntToStr(LFilesList.Count);
    finally
      FreeAndNil(LFilesList);
    end;
  end;
end;

//=============================================================================
//  指定ディレクトリ内のディレクトリを検索して引数のAListに返す
//  Win32APIのFindFirstFileとFindNextFileを使用
//  ディレクトリ名とか属性等の検索条件が必要ならば、その処理を追加する
//=============================================================================
procedure TForm1.GetDirectories(ADir: string; AList: TStringList);
var
  LHandle   : THandle;
  LFileName : String;
  LAttrib   : DWORD;
  LFileData : TWin32FindData;
begin
  ADir    := IncludeTrailingPathDelimiter(ADir);
  LHandle := FindFirstFile(PChar(ADir + '*.*'), LFileData);
  if LHandle <> INVALID_HANDLE_VALUE then begin
    repeat
      LFileName := LFileData.cFileName;
      LAttrib   := LFileData.dwFileAttributes;
      if not ((LFileName = '..') or (LFileName = '.')) then
      begin
        //属性がディレクトリだけならばリストに追加
        if BOOL(LAttrib and FILE_ATTRIBUTE_DIRECTORY) then begin
          //ADir + FileNameとすればフルパス
          AList.Add(LFileName);
        end;
      end;
    until FindNextFile(LHandle, LFileData) = False;
    Windows.FindClose(LHandle);
  end;
end;

//=============================================================================
//  指定ディレクトリ内のファイルを検索して引数のAListに返す
//  Win32APIのFindFirstFileとFindNextFileを使用
//  ファイル名とか属性等の検索条件が必要ならば、その処理を追加する
//=============================================================================
procedure TForm1.GetFiles(ADir: string; AList: TStringList);
var
  LHandle   : THandle;

  LFileName : String;
  LAttrib   : DWORD;
  LFileData : TWin32FindData;
begin
  ADir    := IncludeTrailingPathDelimiter(ADir);
  LHandle := FindFirstFile(PChar(ADir + '*.*'), LFileData);
  if LHandle <> INVALID_HANDLE_VALUE then begin
    repeat
      LFileName := LFileData.cFileName;
      LAttrib   := LFileData.dwFileAttributes;
      if not ((LFileName = '..') or (LFileName = '.')) then begin
        //属性がディレクトリを含んでいなければ全てリストに追加
        if BOOL(LAttrib and FILE_ATTRIBUTE_DIRECTORY) = False then
        begin
          //ADir + FileNameとすればフルパス
          AList.Add(LFileName);
        end;
      end;
    until FindNextFile(LHandle, LFileData) = False;
    Windows.FindClose(LHandle);
  end;
end;

end.




04_指定したパターンに一致するファイル名だけの列挙


TDirectory.GetFiles を使用して、指定したパターンに一致するファイル名だけの一覧を取得するサンプルです。
TDirectory.GetFiles 関数は、その引数で、ワイルドカードや、関数を定義してファイル名の選択ができます。ここでは、指定した拡張子だけのファイル名を列挙します。

[778_ワイルドカードでファイル名をチェック] に、ワイルドカードの使用例があります。



図10
設計時画面
図11
実行例

リスト5
指定したパターンに一致するファイル名だけの列挙
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    ListBox1: TListBox;
    procedure Button1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure ListBox1DblClick(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  FileCtrl,
  Types,
  IOUtils,
  StrUtils,
  ShellAPI;

{$R *.dfm}

//=============================================================================
//  フォーム表示開始時の処理
//  検索条件を設定
//  ここでは複数の拡張子
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  Edit1.Text := '.txt;.doc;.docx;.xls;.xlsx;.rtf';
end;

//=============================================================================
//  指定ディレクトリ内の、検索条件に一致するファイルをリスト
//  TDirectory.GetFilesの引数のルーチンを使用
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LDir       : String;
  LMaskArray : TStringDynArray;
  LPredicate : TDirectory.TFilterPredicate;
  LOption    : TSearchOption;
  LFileName  : String;
  LFilesList : TStringDynArray;
begin
  LDir := ExtractFileDir(Application.ExeName);
  if FileCtrl.SelectDirectory('検索フォルダ指定', '', LDir) then begin
    Screen.Cursor := crHourGlass;
    ListBox1.Items.Clear;
    ListBox1.Items.BeginUpdate;


    //拡張子のリストを作成
    LMaskArray := SplitString(Edit1.Text, ';');
    //-------------------------------------------------------------------------
    //  TDirectory.GetFilesの検索ルーチン
    //  ファイルを検出するたびにこのルーチンが実行される
    //  引数のAPath,ASearchRecを使用して、いろいろな条件設定ができる
    //  戻り値がTrueならファイルリストに追加し、Falseなら追加しない
    //  関数の型はTFilterPredicate
    //-------------------------------------------------------------------------
    LPredicate :=
      function(const APath: String; const ASearchRec: TSearchRec): Boolean
      var
        LMask : String;
      begin
        for LMask in LMaskArray do begin
          if SameText(TPath.GetExtension(ASearchRec.Name), LMask) then begin
            exit(True);
           end;
         end;
         exit(False);
      end;
    //-------------------------------------------------------------------------


    //ADirディレクトリとそのサブディレクトリも検索するオプション
    LOption := TSearchOption.soAllDirectories;
    //ファイルのリストを取得
    LFilesList := TDirectory.GetFiles(LDir, '*.*', LOption, LPredicate);
    for LFileName in LFilesList do begin
      //ファイル名だけならTPath.GetFileName(LFileName)
      ListBox1.Items.Add(LFileName);
    end;
    ListBox1.Items.EndUpdate;
    ListBox1.ScrollWidth := 1000;
    Screen.Cursor := crDefault;
  end;
end;

//=============================================================================
//  ListBox1のOnDblClickイベント処理
//  ファイルを表示してみる
//=============================================================================
procedure TForm1.ListBox1DblClick(Sender: TObject);
var
  LIndex : Integer;
begin
  LIndex := ListBox1.ItemIndex;
  if LIndex < 0 then exit;

  ShellExecute(Handle, '', PChar(ListBox1.Items[LIndex]), nil, nil, SW_SHOW);
end;

end.




05_ファイルの検索 - TDirectory.GetFiles で検索条件設定


ファイルの検索と言ってもファイルのリストと同じです。上のサンプルでは指定したパターンの名前のファイルを列挙していますが、これも検索です。すなわち、ファイル名をリストする操作は、ファイル名の検索をしていることになります。

上のサンプルとほとんど同じですが、指定したファイル名のファイルだけを検索して列挙するサンプルです。下図は、サンプルプログラムのフォルダ直下の AAA というフォルダ内の検索結果です。

TDirectory.GetFiles 関数の第 4 引数で指定するルーチンのコードを変更することにより、いろいろな検索条件のファイルを検索することができます。



[備考]
検索用の関数の機能で検索条件を指定しなくても、全てのファイルをリストしてから、条件に合うファイル名だけを取り出す考え方もあります。


図12
AA1.txt と BB1.txt というファイル名のファイルを検索

リスト6
TDirectory.GetFiles を使用してファイルを検索。いろいろな検索条件をコードで設定できる
指定フォルダ内を、サブフォルダも含めて再帰的に検索
implementation

uses
  FileCtrl,
  Types,
  IOUtils,
  StrUtils;

{$R *.dfm}

//=============================================================================
//  フォーム表示開始時の処理
//  検索条件を設定
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  Edit1.Text := 'AA1.txt;BB1.txt';
  FDir := ExpandFileName('..\AAA');
end;

//=============================================================================
//  指定ディレクトリ内の、検索条件に一致するファイルをリスト
//  TDirectory.GetFilesの引数のルーチンを使用
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LMaskArray : TStringDynArray;
  LPredicate : TDirectory.TFilterPredicate;
  LOption    : TSearchOption;
  LFileName  : String;
  LFilesList : TStringDynArray;
begin
  if FileCtrl.SelectDirectory('検索フォルダ指定', '', FDir) then begin
    Screen.Cursor := crHourGlass;
    ListBox1.Items.Clear;
    ListBox1.Items.BeginUpdate;


    //拡張子のリストを作成
    LMaskArray := SplitString(Edit1.Text, ';');
    //-------------------------------------------------------------------------
    //  TDirectory.GetFilesの検索ルーチン
    //  ファイルを検出するたびにこのルーチンが実行される
    //  引数のAPath,ASearchRecを使用して、いろいろな条件設定ができる
    //  戻り値がTrueならファイルリストに追加し、Falseなら追加しない
    //  関数の型はTFilterPredicate
    //-------------------------------------------------------------------------
    LPredicate :=
      function(const APath: String; const ASearchRec: TSearchRec): Boolean
      var
        LMask : string;
      begin
        for LMask in LMaskArray do begin
          if SameText(TPath.GetFileName(ASearchRec.Name), LMask) then begin
            exit(True);
           end;
         end;
         exit(False);
      end;
    //-------------------------------------------------------------------------


    //FDirディレクトリとそのサブディレクトリも検索するオプション
    LOption := TSearchOption.soAllDirectories;
    //ファイルのリストを取得
    LFilesList := TDirectory.GetFiles(FDir, '*.*', LOption, LPredicate);

    for LFileName in LFilesList do begin
      //ファイル名だけならTPath.GetFileName(LFileName)
      ListBox1.Items.Add(LFileName);
    end;
    ListBox1.Items.EndUpdate;
    ListBox1.ScrollWidth := 1000;
    Screen.Cursor := crDefault;
  end;
end;




06_ファイルの検索 - TDirectory.GetFiles でワイルドカードのみ指定


TDirectory.GetFiles では、詳細な検索処理が必要なければ、第 2 引数の検索パターンだけの指定でも十分利用価値があます。その例です。



図13
BB*.* という検索条件でファイルを検索
  • 検索条件を *.* にすると、サブディレクトリを含めた指定ディレク内の全てのファイルの列挙となる

リスト7
TDirectory.GetFiles を使用して、ワイルドカードのみでファイルを検索
指定フォルダ内を、サブフォルダも含めて再帰的に検索
implementation

uses
  FileCtrl,
  Types,
  IOUtils,
  StrUtils;

{$R *.dfm}

//=============================================================================
//  フォーム表示開始時の処理
//  検索条件を設定
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  Edit1.Text := 'BB*.txt';
  FDir := ExpandFileName('..\AAA');
end;

//=============================================================================
//  指定ディレクトリ内の、検索条件に一致するファイルをリスト
//  TDirectory.GetFilesの引数のルーチンを使用
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LOption    : TSearchOption;
  LPattern   : string;
  LFileName  : string;
  LFilesList : TStringDynArray;
begin
  if FileCtrl.SelectDirectory('検索フォルダ指定', '', FDir) then begin
    Screen.Cursor := crHourGlass;
    ListBox1.Items.Clear;
    ListBox1.Items.BeginUpdate;


    //FDirディレクトリとそのサブディレクトリも検索するオプション
    LOption := TSearchOption.soAllDirectories;

    //検索パターン(ワイルドカード使用可能)
    LPattern := Trim(Edit1.Text);

    //ファイルのリストを取得
    LFilesList := TDirectory.GetFiles(FDir, LPattern, LOption);
    for LFileName in LFilesList do begin
      ListBox1.Items.Add(LFileName);
    end;
    ListBox1.Items.EndUpdate;
    ListBox1.ScrollWidth := 1000;
    Screen.Cursor := crDefault;
  end;
end;




07_ファイルの検索 - FindFirst を使用した Delphi 2009 以前用


上の 2 つのサンプルで使用している TDirectory は Delphi 2010 以降でないと実装されていません。
Delphi 2009 以前では前出のように FindFirst と FindNext が利用できます。
本サンプルのコードも前出のサンプルとほとんど同じですが、参考として掲載しておきます。ワイルドカードで指定したファイル名のファイルを、指定したディレクトリ階層下のサブディレクトリを含めて検索します。検索条件には TMask を使用しています。



図14
BB*.* という検索条件でファイルを検索
  • 検索条件を *.* にすると、サブディレクトリを含めた指定ディレク内の全てのファイルの列挙となる
図15
BB2.txt という名前のファイルを検索

リスト8
FindFirst と FindNext によるファイル名の検索 - Delphi 2009 以前用
指定フォルダ内を、サブフォルダも含めて再帰的に検索
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, ComCtrls;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    ListBox1: TListBox;
    Button1: TButton;
    StatusBar1: TStatusBar;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    FDir : string;
  public
    { Public 宣言 }
    procedure GetDirectoryFiles(ADir, AFileNameMask: string; AList: TStringList);
  end;

var
  Form1: TForm1;

implementation

uses
  FileCtrl,
  Masks;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Text := 'BB*.*';
  FDir := ExpandFileName('..\AAA');
end;

//=============================================================================
//  指定ディレクトリ内のファイルのリスト
//  ワイルドカードに一致するファイル名だけをリスト
//
//  SelectDirectoryの使用にはusesにFileCtrlが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LFilesList : TStringList;
  i          : Integer;
begin
  StatusBar1.Panels[0].Text := '';
  StatusBar1.Update;

  if FileCtrl.SelectDirectory('検索フォルダ指定', '', FDir) then begin
    ListBox1.Items.Clear;
    ListBox1.Items.BeginUpdate;

    LFilesList := TStringList.Create;
    try
      //APath内のディレクトリのファイルリスト作成
      //[マイコンピュータ]等の特殊フォルダを指定した場合は不定となるので注意
      GetDirectoryFiles(FDir, Trim(Edit1.Text), LFilesList);
      LFilesList.Sort;

      //取得したリストをListBoxに表示
      //ListBox1.Items := LFilesList;でも可
      //何か処理を追加する場合はループで回して処理
      for i := 0 to LFilesList.Count - 1 do begin
        ListBox1.Items.Add(LFilesList[i]);
      end;
      ListBox1.Items.EndUpdate;

      //検索結果を表示
      StatusBar1.Panels[0].Text := ' ファイル数 : ' + IntToStr(LFilesList.Count);
    finally
      FreeAndNil(LFilesList);
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  指定ディレクトリ内のファイル名を検索して引数のAListに返す
//  FindFirstとFindNexを使用したサンプル
//  サブディレクトリも検索する場合
//
//  usesにMasksが必要
//-----------------------------------------------------------------------------
procedure TForm1.GetDirectoryFiles(ADir, AFileNameMask: string; AList: TStringList);
var
  LSearchPath   : string;
  LSearchRec    : TSearchRec;
  LFileNameMask : TMask;
  LFullPath     : string;
begin
  LFileNameMask := TMask.Create(AFileNameMask);
  try
    LSearchPath := IncludeTrailingPathDelimiter(ADir) + '*.*';
    if FindFirst(LSearchPath, faAnyFile, LSearchRec) = 0 then begin
      repeat
        if not ((LSearchRec.Name = '..') or (LSearchRec.Name = '.')) then begin
          //ディレクトリだけをリストに追加する
          if (LSearchRec.Attr and faDirectory) = faDirectory then begin
            LFullPath := IncludeTrailingPathDelimiter(ADir) + LSearchRec.Name;
            //パス名を与えて自分自身を呼出(再帰処理)する
            GetDirectoryFiles(LFullPath, AFileNameMask, AList);
          end else begin
            //ここで必要なファイル名だけを取り出す
            if LFileNameMask.Matches(LSearchRec.Name) then begin
              LFullPath := IncludeTrailingPathDelimiter(ADir) + LSearchRec.Name;
              AList.Add(LFullPath);
            end;
          end;
        end;
      until FindNext(LSearchRec) <> 0;
      FindClose(LSearchRec);
    end;
  finally
    FreeAndNil(LFileNameMask);
  end;
end;

end.


FindFirst 関数は、以下のように、ファイルやディレクトリの存在確認にも使用できます。ただし、ディレクトリの場合、末尾にデリミタがあると存在しない結果となります。



[備考]
Delphi 2009 以前の場合、ファイルとディレクトリの存在確認には FileExists、DirectoryExists が一般的に使われているようです。


implementation

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Text := 'C:\Program Files (x86)\Embarcadero\Studio\18.0\bin\bds.exe';
end;

//=============================================================================
//  指定ファイルの存在を確認
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LText : string;
begin
  if FindFileExists(Edit1.Text) then begin
    LText := 'ファイルは存在します';
    MessageBox(Handle, PChar(LText), '検索結果', MB_ICONINFORMATION);
  end else begin
    LText := 'ファイルは存在しません';
    MessageBox(Handle, PChar(LText), '検索結果', MB_ICONWARNING);
  end;
end;

//-----------------------------------------------------------------------------
//  指定ファイルまたはディレクトリの存在を確認する関数
//-----------------------------------------------------------------------------
function TForm1.FindFileExists(AFilePath: string): Boolean;
var
  LSearchRec : TSearchRec;
begin
  Result := False;
  if Length(AFilePath) = 0 then exit;

  //引数のパス名の末尾がデリミタだったら削除する
  if AFilePath[Length(AFilePath)] = PathDelim  then begin
    AFilePath := Copy(AFilePath, 1, Length(AFilePath) - 1);
  end;
  try
    if FindFirst(AFilePath, faAnyFile, LSearchRec) = 0 then begin
      Result := True;
    end;
    FindClose(LSearchRec);
  except
  end;
end;




08_ディレクトリ内の特定条件のファイルを削除


これまでのサンプルを使用して、指定ディレクトリ内のファイル名を配列に格納すれば、特定の条件に一致したファイルだけの削除もできます。
ディレクトリ内のファイル名を配列に格納する際に、取得するファイル名をワイルドカードで指定すれば特定の拡張子だけのファイル名の削除もできることになります。その例です。
もちろん、ファイルの各種プロパティ、例えば更新日時等も判定条件とすることも考えられます。



図16
指定した拡張子のファイルを削除
  • サブディレクトリを含めた指定ディレク内の指定拡張子のファイルを全て削除する

リスト9
指定ディレクトリ内の指定拡張子のファイルを全て削除
implementation

uses
  Types,
  IOUtils,
  StrUtils;

{$R *.dfm}

//=============================================================================
//  指定ディレクトリ内 (サブディレクトリ内を含む) の指定拡張子のファイルを削除
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LDir       : string;
  LOption    : TSearchOption;
  LFileName  : string;
  LFilesList : TStringDynArray;
begin
  LDir := ExpandFileName('..\AAA');
  Screen.Cursor := crHourGlass;

  //FDirディレクトリとそのサブディレクトリも検索するオプション
  LOption := TSearchOption.soAllDirectories;
  // 指定拡張子だけのファイル名の配列を取得
  LFilesList := TDirectory.GetFiles(LDir, '*.TXT', LOption);

  // ファイルを削除していく
  for LFileName in LFilesList do begin
    TFile.Delete(LFileName);
  end;

  Screen.Cursor := crDefault;
end;


ファイル名の配列の各要素の値、つまりファイル名が特定の条件と一致する場合にそのファイルを削除する例です。ここでは上の例と同じで、指定した拡張子のファイルだけを削除して、削除しなかったファイル名だけをリストします。



図17
ディレクトリ内の特定条件のファイルを削除
  • 削除しなかったファイル名だけをリストアップした結果

リスト10
指定ディレクトリ内の特定条件のファイルを削除
削除するファイルをループ処理内で判定する例
implementation

uses
  Types,
  IOUtils,
  StrUtils;

{$R *.dfm}

//=============================================================================
//  指定ディレクトリ内 (サブディレクトリ内を含む) の指定条件のファイルを削除
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LDir       : string;
  LOption    : TSearchOption;
  LFileName  : string;
  LFilesList : TStringDynArray;
begin
  LDir := ExpandFileName('..\AAA');
  Screen.Cursor := crHourGlass;
  ListBox1.Items.Clear;
  ListBox1.Items.BeginUpdate;


  //FDirディレクトリとそのサブディレクトリも検索するオプション
  LOption := TSearchOption.soAllDirectories;
  // ファイル名の配列を取得
  LFilesList := TDirectory.GetFiles(LDir, '*.*', LOption);

  // ファイル名の配列のファイル名を取得して処理 
  for LFileName in LFilesList do begin   
    if UpperCase(ExtractFileExt(LFileName)) = '.TXT' then begin
      // 条件に一致したらそのファイルを削除
      TFile.Delete(LFileName);
    end else begin
      // 削除しないで残すファイルだけをリスト
      ListBox1.Items.Add(LFileName);
    end;
  end;

  ListBox1.Items.EndUpdate;
  ListBox1.ScrollWidth := 1000;
  Screen.Cursor := crDefault;
end;


上のサンプルでは削除対象のファイルの存在を確認していません。一般的には以下のように、ファイルに対して操作する場合は、そのファイルの存在を確認します。存在しないファイルに対して操作を行うとエラーや例外が発生することがあります。



リスト11
  // ファイルを削除していく
  for LFileName in LFilesList do begin
    // 該当ファイルが存在していれば削除
    if TFile.Exists(LFileName) then begin
      TFile.Delete(LFileName);
    end;
  end;




09_ディレクトリのコピー

以下は指定ディレクトリを別のディレクトリにコピーする例です。指定したディレクリ内のファイルを、サブディレクトリ内のファイルも含めて別のディレクトリにコピーします。
TDirectory.Copy 関数を使用すると、コピー先のディレクトリが存在しない場合、自動的にディレクトリを作成してくれます。TDirectory.Copy 関数によるコピーは上書きコピーとなります。

TDirectory.Move 関数を使用してディレクトリを移動する時も、移動先のディレクトリが存在しない場合は自動でディレクトリを作成してくれます。



[備考]
SHFileOperation 関数を使用してディレクトリやファイルのコピー、貼り付け、移動や削除等の操作行うと、エクスプローラによる操作の時のように進捗ダイアログを表示することができます。


リスト12
ディレクトリのコピー
implementation

uses
  IOUtils;

{$R *.dfm}

//=============================================================================
//  指定ディレクトリの内容をそのまま別のディレクトリにコピー
//
//  TDirectory.Copy はコピー先のディレクトリがなければ自動的に作成してくれる
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LSrcDir  : string;
  LDestDir : string;
  LText    : string;
begin
  LSrcDir  := 'd:\000\AAA';
  LDestDir := 'd:\000\GGG';

  if not TDirectory.Exists(LSrcDir) then begin
    LText := 'コピー元のディレクトリが存在しません';
    MessageBox(Handle, PChar(LText), '情報', MB_ICONINFORMATION);
    Exit;
  end;

  // ディレクトリ内容を上書きコピー  
  TDirectory.Copy(LSrcDir, LDestDir);
end;




10_ファイルのコピーとディレクトリの存在確認


TFile.Copy 関数を使用してファイルをコピーする場合は、コピー先のディレクトリが存在しないと例外が発生してコピーできません。コピー先のディレクトリが存在しない場合は、以下のように、ディレクトリを作成する必要があります。
TFile.Move 関数を使用してファイルを移動する時も同じです。
TFile.Move 関数でコピー元とコピー先のディレクトリ名が同じで、別のファイル名にすればファイル名の変更となります。

ファイルの存在のチェックには以下の関数類が利用できます。



リスト13
ファイルのコピー
implementation

uses
  IOUtils;

{$R *.dfm}

//=============================================================================
//  ファイルのコピー
//  TFile.Copy はコピー先のディレクトリが存在しない場合は例外が発生する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LSrcFile  : string;
  LDestFile : string;
  LDestDir  : string;
  LText     : string;
begin
  LSrcFile  := 'd:\000\AAA\BB1\AA3.txt';
  LDestFile := 'd:\000\GGG\BB1\AA3.txt';

  if not TFile.Exists(LSrcFile) then begin
    LText := 'コピー元のファイルが存在しません';
    MessageBox(Handle, PChar(LText), '情報', MB_ICONINFORMATION);
    Exit;
  end;


  // コピー先のディレクトリの存在をチェック
  LDestDir := TPath.GetDirectoryName(LDestFile);
  if not TDirectory.Exists(LDestDir) then begin
    // ディレクトリを作成
    TDirectory.CreateDirectory(LDestDir);
  end;

  // ファイルを上書きコピー
  TFile.Copy(LSrcFile, LDestFile, True);
end;


以下は、ディレクトリ部分とディレクトリ部分を除いたファイル名に分解して操作する例です。
コピー元のファイルの種類によってコピー先のディレクトリを変更したい場合、あるいはファイル名を変更してコピーしたい場合等に参考になるかも知れません。



リスト14
ディレクトリ部分とファイル名を分解操作してファイルをコピーする例
implementation

uses
  IOUtils;

{$R *.dfm}

//=============================================================================
//  ファイルのコピー
//  ディレクトリ部分とファイル名部分を分解して操作する例
//  TFile.Copy はコピー先のディレクトリが存在しない場合は例外が発生する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LSrcFile     : string;
  LSrcFileName : string;
  LDestFile    : string;
  LDestDir     : string;
  LText        : string;
begin
  LSrcFile := 'd:\000\AAA\BB1\AA3.txt';
  LDestDir := 'd:\000\GGG\BB1';

  if not TFile.Exists(LSrcFile) then begin
    LText := 'コピー元のファイルが存在しません';
    MessageBox(Handle, PChar(LText), '情報', MB_ICONINFORMATION);
    Exit;
  end;

  // コピー元の (ディレクトリ部を除いた) ファイル名だけを取り出す
  LSrcFileName := TPath.GetFileName(LSrcFile);

 // コピー先のディレクトリの存在をチェック
  if not TDirectory.Exists(LDestDir) then begin
    // ディレクトリを作成
    TDirectory.CreateDirectory(LDestDir);
  end;


  // コピー先のフルパス名を作成
  LDestFile := TPath.Combine(LDestDir, LSrcFileName);

  // ファイルを上書きコピー
  TFile.Copy(LSrcFile, LDestFile, True);
end;




11_ディレクトリの作成と削除


前項のサンプルでも使用しているように、ディレクトリは TDirectory.CreateDirectory 関数で作成できます。TDirectory.Delete 関数でディレクトリを、サブディレクトリも含めて削除できます。



図18
ディレクトリの作成と削除

リスト15
ディレクトリの作成と削除
implementation

uses
  IOUtils;

{$R *.dfm}

//=============================================================================
//  ディレクトリの作成と削除
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LDir     : string;
  LNewDir  : string;
  LMsgText : string;
  LRet     : Integer;
begin
  LDir    := 'd:\000\AAA';
  LNewDir := 'd:\000\GGG\123\BBB';

  if not TDirectory.Exists(LDir) then begin
    LMsgText := '指定したパスが見つかりません';
    MessageBox(Handle, PChar(LMsgText), 'エラー', MB_ICONERROR);
    Exit;
  end;

  LRet := IDYES;
  if not TDirectory.IsEmpty(LDir) then begin
    LMsgText := 'ディレクトリが空ではありません' + sLineBreak + '削除しますか';
    LRet := MessageBox(Handle, PChar(LMsgText), '確認', MB_YESNO or MB_ICONQUESTION);
  end;

  // ディレクトリを (サブディレクトリを含めて) 削除
  if LRet = IDYES then begin
    TDirectory.Delete(LDir, True);
    Application.ProcessMessages;
  end;

  // ディレクトリを作成
  TDirectory.CreateDirectory(LNewDir);
end;