[BlueLeaf1336]> PROGRAM>

エクスプローラからのドラッグ&ドロップ 2

historyTOP

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;

EOFTOP