外部アプリから Delphi のコンポーネントへドラッグ＆ドロップする方法【TDropFileクラス公開】

## ✅ はじめに

Delphi では、ファイルをフォームやコンポーネントにドラッグ＆ドロップで渡す `WM_DROPFILES` メッセージが使えますが、**外部アプリからのドロップ**に対応したクラス設計の情報は少なく、実用的なサンプルもほとんど見つかりません。

そこで本記事では、**VCL の任意の `TWinControl` 派生コンポーネントに対応した汎用 D&D クラス `TDropFile`** を紹介します。

## 🎯 対象読者

- Delphi（VCL）でアプリを作成している方
- 外部アプリからのファイルドロップを簡単に実装したい方
- 複数コンポーネントに対応した共通クラス設計を求めている方

## 📦 機能概要

- 外部アプリ（例：エクスプローラー）からのファイルドロップに対応
- 任意の `TWinControl` に `Attach` して処理を追加できる
- イベントでドロップされたファイルの一覧を受け取れる
- 元の `WindowProc` を保存・復元するため他の処理と干渉しにくい
- デバッグ用ログ出力（`OutputDebugString`）あり

## 🧱 ソースコード：DropFile.pas

<details>
<summary>クリックして展開</summary>

```pascal
unit DropFile;

interface

uses
  Winapi.Windows, Winapi.Messages, Winapi.ShellAPI,
  System.Classes, System.Generics.Collections,
  Vcl.Controls, Vcl.Forms;

type
  TDropFileEvent = procedure(Sender: TObject; Control: TWinControl; const FileNames: TArray<string>) of object;

  TDropTargetInfo = class
    Control: TWinControl;
    OriginalWndProc: TWndMethod;
    procedure CustomWndProc(var Msg: TMessage);
    constructor Create(AControl: TWinControl; ACallback: TDropFileEvent);
  private
    FCallback: TDropFileEvent;
  end;

  TDropFile = class
  private
    FTargets: TObjectList<TDropTargetInfo>;
    FOnDropReceived: TDropFileEvent;
    function FindTarget(Control: TWinControl): TDropTargetInfo;
    procedure Log(const S: string);
  public
    constructor Create;
    destructor Destroy; override;

    procedure Attach(Control: TWinControl);
    procedure Detach(Control: TWinControl);

    property OnDropReceived: TDropFileEvent read FOnDropReceived write FOnDropReceived;
  end;

implementation

uses
  System.SysUtils;

{ TDropTargetInfo }

constructor TDropTargetInfo.Create(AControl: TWinControl; ACallback: TDropFileEvent);
begin
  inherited Create;
  Control := AControl;
  OriginalWndProc := Control.WindowProc;
  FCallback := ACallback;
end;

procedure TDropTargetInfo.CustomWndProc(var Msg: TMessage);
var
  DropMsg: TWMDropFiles absolute Msg;
  Count, I: Integer;
  Buffer: array[0..MAX_PATH] of Char;
  Files: TArray<string>;
begin
  if Msg.Msg = WM_DROPFILES then
  begin
    Count := DragQueryFile(DropMsg.Drop, $FFFFFFFF, nil, 0);
    SetLength(Files, Count);
    for I := 0 to Count - 1 do
    begin
      DragQueryFile(DropMsg.Drop, I, Buffer, MAX_PATH);
      Files[I] := Buffer;
    end;
    DragFinish(DropMsg.Drop);

    if Assigned(FCallback) then
      FCallback(nil, Control, Files);
  end;
  OriginalWndProc(Msg);
end;

{ TDropFile }

constructor TDropFile.Create;
begin
  inherited Create;
  FTargets := TObjectList<TDropTargetInfo>.Create(True);
end;

destructor TDropFile.Destroy;
begin
  while FTargets.Count > 0 do
    Detach(FTargets[0].Control);
  FTargets.Free;
  inherited;
end;

function TDropFile.FindTarget(Control: TWinControl): TDropTargetInfo;
var
  Info: TDropTargetInfo;
begin
  for Info in FTargets do
    if Info.Control = Control then
      Exit(Info);
  Result := nil;
end;

procedure TDropFile.Attach(Control: TWinControl);
var
  Info: TDropTargetInfo;
begin
  if Assigned(FindTarget(Control)) then Exit;

  Info := TDropTargetInfo.Create(Control, FOnDropReceived);
  Control.WindowProc := Info.CustomWndProc;
  DragAcceptFiles(Control.Handle, True);
  FTargets.Add(Info);
  Log('Attached: ' + Control.Name);
end;

procedure TDropFile.Detach(Control: TWinControl);
var
  Info: TDropTargetInfo;
begin
  Info := FindTarget(Control);
  if not Assigned(Info) then Exit;

  Control.WindowProc := Info.OriginalWndProc;
  DragAcceptFiles(Control.Handle, False);
  FTargets.Remove(Info);
  Log('Detached: ' + Control.Name);
end;

procedure TDropFile.Log(const S: string);
begin
  {$IFDEF DEBUG}
  OutputDebugString(PChar('[TDropFile] ' + S));
  {$ENDIF}
end;

end.

```

</details>

## 🧪 使用例

```pascal
procedure TForm1.FormCreate(Sender: TObject);
begin
  DropFile := TDropFile.Create;
  DropFile.Attach(ListBox1); // ← 任意の TWinControl に対応
  DropFile.OnDropReceived :=
    procedure(Sender: TObject; Control: TWinControl; const FileNames: TArray<string>)
    var
      FN: string;
    begin
      for FN in FileNames do
        ListBox1.Items.Add(FN);
    end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  DropFile.Free;
end;
```

## 💡 注意点・応用

- `WM_DROPFILES` によるファイルドロップは、あくまで「**ファイルパス**」だけを受け取る形式です。テキストや画像そのものは扱えません。
- 複数コンポーネントに `Attach` してもOK。`Detach` を忘れずに。
- ドロップ処理中にアクセス違反が出る場合、`Control.Handle` がすでに解放されていないか注意してください。

## 📘 まとめ

この `TDropFile` クラスを使えば、Delphi の標準コンポーネントに手軽に外部ファイルドロップを組み込めます。フォーム、リストボックス、パネルなど、どんな `TWinControl` でも対応可能です。

実装例やご質問などあればコメントでどうぞ！

## 🖇 参考

- [Windows API - DragAcceptFiles](https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragacceptfiles)
- Delphiヘルプ：WM_DROPFILES, DragQueryFile, DragFinish など