history | TOP |
2008/01/01:作成
はじめに | TOP |
今年最初のねたです。エクスプローラからのドラッグ&ドロップは、Windowsを使う上で非常に有効な処理ですが、結構面倒です。特にフォルダを再帰的に掘り進めていくのは、定型処理の割に理由のわからないメンドくささがあります。
今回はそのメンドくささにけりをつけるつもりで、ドラッグ&ドロップの処理が必要なアプリのベースにできそうなプロジェクトを作ってみました。
結局のところ、今まで何度も書いてることの焼き直しですが、すこーしだけ色をつけてみました。
WMDropFiles のメッセージイベントで再帰処理をやってしまうと、ファイルが多くなったときやフォルダの階層が深くなったときに(たしか)動作がとまってしまいます。なので、ドロップされたパスだけをいったん保持して、そこからのファイルの検索自体はメインスレッドとは別のスレッドでバックグラウンドでやってみようと。ただ、そうすると、その別のスレッドから検索結果を受け取らないといけないわけで、なんとも微妙ですが...
両方の場合で作ってみて、操作感を比べてみようかと思ったんですが、邪魔くさくてあきらめました。
サンプル画面とダウンロード | TOP |
20080101DropFolder.zip(173,529Bytes) テストに使ったソースコードと実行ファイルです。
こんな画面です。ファイルやフォルダをドロップすると、ファイルはそのまま追加して、フォルダの場合は最下層まで掘り進んでいきます。で、すべてのファイルを列挙します。ここでは、C:¥Windows¥ フォルダをドロップしました。もちろん途中で我慢できずにキャンセルしました。
今気づきましたが、フォルダを列挙することはできないです。
残念ながら動作確認は不完全ですが、キャンセルはききます。
ソースコード(検索スレッド) | TOP |
//============================================================================= // ファイルを再帰的に検索するスレッド // 2008.01.01 BlueLeaf1336 fjtkt@ybb.ne.jp //============================================================================= unit FindFile; interface uses Windows, Classes, SysUtils, SyncObjs; //============================================================================= // Subject: [Delphi-ML:4452] Re: ディレクトリ構造を得るには? //============================================================================= type TFindState = (fsIdle, // 何もしてない(いつでも終了できる) fsFind // 検索中(終了前に検索中断する必要がある) ); TFindFileThread = class(TThread) private // 検索対象のパス FPathList: TStringList; // 検索結果 FResultList: TStringList; // 状態(検索中かどうか) FFindState: TFindState; // 掘り下げるレベル(負: 無限 0: 掘り下げない 正: 掘り下げる) FMaxLevel: Integer; // 検索中のフォルダ FCurrentPath: String; // 検索を中断させるフラグ FCancel: Boolean; public // コンストラクタ constructor Create(); // デストラクタ destructor Destroy(); override; public // 外部から検索対象のパスを追加する procedure PutPath(const APath: String); // 外部から検索をキャンセルする(検索中断と FPathListクリア) procedure Cancel(); // 外部から検索結果を取り出す procedure GetResult(AResult: TStrings; const ACount: Integer = 100); protected // パスをひとつずつ FindFiles に渡す procedure Execute(); override; // 検索本体 procedure FindFiles(const APath: String; const ALevel: Integer); // 検索リストの件数 function GetResultCount(): Integer; public // 外部から掘り下げるレベルを変更する property MaxLevel: Integer read FMaxLevel write FMaxLevel; // 外部から検索中のフォルダを確認する property CurrentPath: String read FCurrentPath; // 外部から検索リストの件数を確認する property ResultCount: Integer read GetResultCount; end; var GFindFileThread: TFindFileThread = nil; implementation var CS: TCriticalSection; //============================================================================= // TFindFileThread implementation //============================================================================= //----------------------------------------------------------------------------- // コンストラクタ constructor TFindFileThread.Create(); begin inherited Create(True); FPathList := TStringList.Create(); FPathList.Sorted := True; FPathList.Duplicates := dupIgnore; FResultList := TStringList.Create(); FResultList.Duplicates := dupIgnore; FFindState := fsIdle; FMaxLevel := -1; FCurrentPath := ''; FCancel := False; // FormCreate でプロパティ設定後 Resume(); //Resume(); end; //----------------------------------------------------------------------------- // デストラクタ destructor TFindFileThread.Destroy(); begin FPathList.Free(); FResultList.Free(); end; //----------------------------------------------------------------------------- // 外部から検索対象のパスを追加する procedure TFindFileThread.PutPath(const APath: String); begin CS.Enter(); // フォルダなら検索対象リストに追加 if (DirectoryExists(APath)) then begin FPathList.Add(IncludeTrailingPathDelimiter(APath)); end else // ファイルなら結果リストに追加 if (FileExists(APath)) then begin FResultList.Add(APath); end; CS.Leave(); end; //----------------------------------------------------------------------------- // 外部から検索をキャンセルする(検索中断と FPathListクリア) procedure TFindFileThread.Cancel(); begin CS.Enter(); // 検索対象のリストもクリア FPathList.Clear(); // 結果リストもクリア FResultList.Clear(); CS.Leave(); // 検索中なら if (FFindState = fsFind) then begin // 中断させる FCancel := True; end; end; //----------------------------------------------------------------------------- // 外部から検索結果を取り出す procedure TFindFileThread.GetResult(AResult: TStrings; const ACount: Integer); var i: Integer; LCount: Integer; begin CS.Enter(); // 取り出す件数を決定 LCount := ACount; if (LCount > FResultList.Count) then begin LCount := FResultList.Count; end; // 取り出してメンバのリストから削除 for i := 0 to LCount - 1 do begin AResult.Add(FResultList[0]); FResultList.Delete(0); end; CS.Leave(); end; //----------------------------------------------------------------------------- // パスをひとつずつ FindFiles に渡す procedure TFindFileThread.Execute(); var LPath: String; begin while (not Terminated) do begin Sleep(1); FCurrentPath := ''; if (FPathList.Count > 0) then begin // 検索対象のリストからひとつ取り出す CS.Enter(); LPath := FPathList[0]; FPathList.Delete(0); CS.Leave(); // 検索実行 FFindState := fsFind; FindFiles(LPath, 0); FFindState := fsIdle; FCancel := False; end; end; end; //----------------------------------------------------------------------------- // 検索本体 procedure TFindFileThread.FindFiles(const APath: String; const ALevel: Integer); var LPath: String; LRec: TSearchRec; begin LPath := IncludeTrailingPathDelimiter(APath); if (not DirectoryExists(LPath)) then Exit; // 検索中のパス FCurrentPath := APath; // なんか入ってるかな? if FindFirst(LPath + '*.*', faAnyFile, LRec) = 0 then begin try repeat // レベルチェック if (FMaxLevel >= 0) and (FMaxLevel < ALevel) then Exit; // キャンセルチェック if (FCancel) then Exit; // 自フォルダと親フォルダは無視 if (LRec.Name = '.') or (LRec.Name = '..') then Continue; // フォルダだったら if ((LRec.Attr and faDirectory) <> 0) then begin // 掘り下げる FindFiles(LPath + LRec.Name, ALevel + 1); end // ファイル else if (FileExists(LPath + LRec.Name)) then begin // 結果リストに追加 CS.Enter(); FResultList.Add(LPath + LRec.Name); CS.Leave(); end; until FindNext(LRec) <> 0; finally FindClose(LRec); end; end; end; //----------------------------------------------------------------------------- // 検索リストの件数 function TFindFileThread.GetResultCount(): Integer; begin Result := FResultList.Count; end; //----------------------------------------------------------------------------- // 開始・終了処理 initialization CS := TCriticalSection.Create(); GFindFileThread := TFindFileThread.Create(); finalization GFindFileThread.Free(); CS.Free(); end.
ソースコード(フォーム側) | TOP |
//============================================================================= // TMainForm implementation //============================================================================= //----------------------------------------------------------------------------- // エクスプローラからのドラッグアンドドロップを受け付けるようにする // http://www2.big.or.jp/~osamu/Delphi/tips.cgi?index=0218.txt procedure TMainForm.CreateParams(var Params: TCreateParams); begin inherited CreateParams(Params); Params.ExStyle := Params.EXStyle or WS_EX_ACCEPTFILES; end; //----------------------------------------------------------------------------- // フォーム作成時 procedure TMainForm.FormCreate(Sender: TObject); begin FTimerThread := TTimerThread.Create(False); GFindFileThread.Resume(); end; //----------------------------------------------------------------------------- // フォーム破棄時 procedure TMainForm.FormDestroy(Sender: TObject); begin FTimerThread.Free(); // 検索中ならキャンセル GFindFileThread.Cancel(); end; //----------------------------------------------------------------------------- // エクスプローラからドロップされた時の処理 // http://www2.big.or.jp/~osamu/Delphi/tips.cgi?index=0218.txt procedure TMainForm.WMDropFiles(var Msg: TWMDropFiles); var LPathName: Array[0..MAX_PATH] of Char; i, LCount: integer; begin // ドロップされたファイルの数を取得する LCount := DragQueryFile(Msg.Drop, DWORD(-1), LPathName, SizeOf(LPathName)); for i := 0 to LCount - 1 do begin // それぞれのファイル名を取得する DragQueryFile(Msg.Drop, i, LPathName, SizeOf(LPathName)); // 検索スレッドに追加 GFindFileThread.PutPath(LPathName); end; // ドロップ完了 DragFinish(Msg.Drop); end; //----------------------------------------------------------------------------- // キャンセル procedure TMainForm.Button1Click(Sender: TObject); begin GFindFileThread.Cancel(); end; //----------------------------------------------------------------------------- // 検索スレッドから結果を取り出す procedure TMainForm.UpdateList(); begin Label1.Caption := GFindFileThread.CurrentPath; if (GFindFileThread.ResultCount > 0) then begin ListBox1.Items.BeginUpdate(); GFindFileThread.GetResult(ListBox1.Items); ListBox1.Items.EndUpdate(); end; end;
EOF | TOP |