TDirectory.GetFiles Function

「特定ファイルの名称をフルパス付きで取得したい!」

例えば、特定のフォルダ内にある拡張子が「jpg」のファイルの名称と、そのファイルまでのフルパスをイッキに全部!取得したい・・・なんて場合の覚書。

1.特定のフォルダ内にあるJpeg画像名を全部取得
2.サブフォルダがあったらその中も探す
3.ListBoxに高速で項目を追加する方法
4.まとめ
5.お願いとお断り

1.特定のフォルダ内にあるJpeg画像名を全部取得

ある特定のフォルダにある拡張子が「jpg」のファイル全てについて、そこまでのフルパスを取得してListBoxに表示する方法は次の通り。

FormにButtonとListBoxを一つずつ追加して、以下を記述。

implementation

uses
  System.Types,
  System.IOUtils,
  System.StrUtils,
  System.Masks,
  Vcl.FileCtrl,
  System.UITypes;

  //System.TypesはTStringDynArrayを使うために追加
  //System.IOUtilsはTDirectory.TFilterPredicateを使うために追加
  //System.StrUtilsはSplitStringを使うために追加
  //System.MasksはMatchesMaskを使うために追加
  //Vcl.FileCtrlはSelectDirectoryを使うために追加
  //System.UITypesはMessageDlgを使うために追加

{$R *.dfm}

procedure TForm1.ButtonXClick(Sender: TObject);
var
  //フォルダの選択
  iStartFolder: string;
  iDirectories: TArray<string>;
  //ファイルリストの取得
  FileNames: TStringDynArray;
  strFileName: String;

  //ファイルを検索
  function MyGetFiles(const Path, Masks: string): TStringDynArray;
  var
    MaskArray: TStringDynArray;
    Predicate: TDirectory.TFilterPredicate;
  begin
    MaskArray := SplitString(Masks, ';');
    Predicate :=
      function(const Path: string; const SearchRec: TSearchRec): Boolean
      var
        Mask: string;
      begin
        for Mask in MaskArray do
          if MatchesMask(SearchRec.Name, Mask) then
            exit(True);
        exit(False);
      end;
    Result := TDirectory.GetFiles(Path, Predicate);
  end;
begin
  //フォルダを選択
  iStartFolder:=ExtractFilePath(Application.ExeName);
  if SelectDirectory(iStartFolder, iDirectories,
    [sdHidePinnedPlaces, sdNoDereferenceLinks, sdForceShowHidden,
    sdAllowMultiselect], 'フォルダを選択してください', 'Folder', 'Ok') then
  begin
    FileNames:=MyGetFiles(iDirectories[0], '*.jpg');
    for strFileName in FileNames do
    begin
      ListBox1.Items.Add(strFileName);
    end;
  end else begin
    //確認(キャンセルされた時に何かしたい場合)
    MessageDlg('キャンセルされました', mtInformation, [mbOk] , 0);
  end;
end;

たくさんのファイルを扱う場合には、ファイルへのPathとそれぞれに異なるファイル名の処理がまず問題になるが、上記の方法を用いれば、用意したListBoxに指定した拡張子のファイルのみ、フルパス付きでリストが作成される。TListBoxを表示する必要がなければVisibleプロパティをFalseにしておけば、その存在はまったく気にならないし、あとは、使いたい時にItemIndexやItems.Countを参照するだけでOKだから、Pathとファイル名の取得についてはもうなぁーんにも気にすることがなくなり、非常に便利!

例えば、以下の通り。

  //ファイルの数だけLoopする
  for i := 0 to ListBox1.Items.Count-1 do
  begin
    ShowMessage(ListBox1.Items[i]);
  end;

2.サブフォルダがあったらその中も探す

上の例のTDirectory.GetFiles関数では検索する際、サブフォルダを無視しているが、引数の指定を次のように変更すれば、サブフォルダ内も検索できる。

procedure TForm1.ButtonXClick(Sender: TObject);
var
  //フォルダの選択
  iStartFolder: string;
  iDirectories: TArray<string>;
  //ファイルリストの取得
  FileNames: TStringDynArray;
  strFileName: String;
  //検索するファイルの拡張子を指定
  SearchPattern: string;
  //サブフォルダも検索
  Option: TSearchOption;
begin
  //初期化
  ListBox1.Items.Clear;
  //フォルダを選択
  iStartFolder:=ExtractFilePath(Application.ExeName);
  if SelectDirectory(iStartFolder, iDirectories,
    [sdHidePinnedPlaces, sdNoDereferenceLinks, sdForceShowHidden,
    sdAllowMultiselect], 'フォルダを選択してください', 'Folder', 'Ok') then
  begin
    SearchPattern:= '*.jpg';
    //検索モード
    //Option:= TSearchOption.soTopDirectoryOnly; //指定フォルダ直下のみ
    Option:= TSearchOption.soAllDirectories; //サブフォルダ内も検索
    //指定拡張子のファイル名をフルパス付きで取得
    FileNames:= TDirectory.GetFiles(iDirectories[0], SearchPattern, Option);
    for strFileName in FileNames do
    begin
      ListBox1.Items.Add(strFileName);
    end;
  end else begin
    //確認(キャンセルされた時に何かしたい場合)
    MessageDlg('キャンセルされました', mtInformation, [mbOk] , 0);
  end;
end;

どうやらTDirectory.GetFiles関数はいろんな引数を指定できるらしい。
せっかくだから調べてみた。

System.IOUtils.TDirectory.GetFiles

https://docwiki.embarcadero.com/Libraries/Sydney/ja/System.IOUtils.TDirectory.GetFiles
以下、上記Webサイトより引用

class function GetFiles(const Path: string): TStringDynArray;
class function GetFiles(const Path: string;  const Predicate: TFilterPredicate): TStringDynArray;
class function GetFiles(const Path, SearchPattern: string): TStringDynArray;
class function GetFiles(const Path, SearchPattern: string;  const Predicate: TFilterPredicate): TStringDynArray;
class function GetFiles(const Path, SearchPattern: string;  const SearchOption: TSearchOption): TStringDynArray; overload; static;
class function GetFiles(const Path, SearchPattern: string;  const SearchOption: TSearchOption; const Predicate: TFilterPredicate): TStringDynArray; overload; static;
class function GetFiles(const Path: string;  const SearchOption: TSearchOption; const Predicate: TFilterPredicate): TStringDynArray; overload; static;

こんなにあったんだ。びっくり☆

もし、検索対象フォルダが決まっているのであれば、さらに簡単に・・・
(サブフォルダまで検索するか・どうかはOptionを切り替えて指定)

procedure TForm1.ButtonXClick(Sender: TObject);
var
  Path:string;
  SearchPattern:string;
  Option:TSearchOption;
  FileNames:TStringDynArray;
  FileName:String;
begin
  //初期化
  ListBox1.Items.Clear;
  //検索先のPathは指定
  Path:=ExtractFilePath(Application.ExeName) + 'Data';
  //ファイル名に一致する検索パターン
  SearchPattern:='*.jpg';
  //検索モード
  //Option:=TSearchOption.soTopDirectoryOnly; //指定フォルダ直下のみ
  Option:=TSearchOption.soAllDirectories; //サブフォルダ内も検索
  //ファイルのリストを作成
  FileNames:=TDirectory.GetFiles(Path, SearchPattern, Option);
  for FileName in FileNames do
  begin
    ListBox1.Items.Add(FileName);
  end;
end;

3.ListBoxに高速で項目を追加する方法

ファイルのリスト作成をより一層高速化するには、以下のようにリストをAddするfor文の前後にListBox1.Items.BeginUpdate / EndUpdateを入れると良いそうで・・・
(TListBoxのStyleプロパティがlbStandardの場合)

  ListBox1.Items.BeginUpdate;
  for FileName in FileNames do
  begin
    ListBox1.Items.Add(FileName);
  end;
  ListBox1.Items.EndUpdate;

あくまでもMyPC環境での値だが、1000枚のJpeg画像の名称を取得するのに、UpDate命令なしの場合が5回平均で120ms、UpDate命令ありの場合が同じく5回平均で17ms。

たった1000枚でこれだけの差。ファイルの数が増えれば増えるほど、その差が大きくなるのは自明の理。

これはTListBoxだけでなく、TMemo等のDelphiのコンポーネントに多数の項目を追加または変更する際に共通して起きる現象で、発生理由は、変更のたびに画面が再描画されるためとのこと。例えば、今回のようにListBoxに多数の項目を追加すると追加項目数分の再描画が発生し、項目数が多いほど処理時間が必要に。

そこでアイテムに変更を加える前に、 BeginUpdateを呼び出し、すべての変更が完了したら、EndUpdateを呼び出して変更を画面に表示するように指定すると画面の再描画が抑止され、処理時間は大幅に短縮される・・・ということで、大量の項目の追加・変更を行うときは忘れずにUpDate命令を使ったほうがよさそう。

ちなみにTMemoの場合であれば・・・

  Memo1.Lines.BeginUpdate;
  for i := 1 to 5000 do
  begin
    Memo1.Lines.Add('あいうえお');
  end;
  Memo1.Lines.EndUpdate;

つまり、BeginUpdateが画面更新の停止で、EndUpdateが再開ってことでいいのかな?
私はそんなふうに理解しました☆

4.まとめ

ある特定のフォルダ内にある、特定の拡張子を持つファイルのリストを作成したい場合は、TDirectory.GetFiles関数が便利。
取得したフルパス付きのファイル名はListBoxに入れておけば、ItemIndexやItems.Countを参照するだけで具体的な名前やPathを気にせずに使えてこれも便利。
ListBoxに大量に項目を追加する時は、BeginUpDateとEndUpDateを忘れずに使おう! というお話でした。

5.お願いとお断り

このサイトの内容を利用される場合は、自己責任でお願いします。記載した内容を利用した結果、利用者および第三者に損害が発生したとしても、このサイトの管理者は一切責任を負えません。予め、ご了承ください。