Delphi でドラッグ&ドロップを可能にするまでの長い道程

 Delphi で作ったプログラムに、何かのファイルをロードさせようとすると、 ファイルのドラッグ&ドロップが、まず使いたくなってきます。 しかし、Delphi のヘルプにはその方法が載ってません。 Win32API のメッセージを処理しなければならないからです。
 Delphi6 Personal(Free版)を使っていた僕も、付いてきた英語版の Win32API ヘルプはもちろん、Delphi1 に付属していた、Win3.1 API ヘルプ(日本語)を見ても、全くちんぷんかんぷんで困っていました。 ウェブを探しても、色んな説があって、どれが正解なのか分からないまま。
 しかし、たまたま本屋で手に取った雑誌に Delphi での DandD の実現方法が 載っており、早速買ってサンプルプログラムを作り、自分の覚書のためにも、 ここに書いておく事にしました。
 尚、簡単のためドロップされたファイルが1つだけの場合用のコードにしています。 複数のファイルをドロップされた時のコードはファイルの数だけ for ループで ファイル名を取得する必要があります。

1、まず使用するユニット( uses 節)に、 ShellAPI を追加します。

uses
  Windows, Messages, SysUtils, .......... ShellAPI;
2、インターフェース部に、protected 宣言を作り、プロシージャを宣言します。 本体は後程書きます。メッセージを扱うプロシージャは protected にするのが 基本だそうです。

    :
private
  { Private 宣言 }
protected
  procedure FilesDropped(var Msg1:TWMDropFiles); message WM_DROPFILES;
public
  { Public 宣言 }
    :
3、メインフォームの Create プロシージャで DragAcceptFiles を TRUE で 実行します。これで、ドラッグされたファイルをウィンドウが受け取れる、 と Windows に登録されます。

procedure TForm1.FormCreate(Sender : TObject);
begin
    :
  DragAcceptFiles(Handle, TRUE);
    :
end;

4、先に宣言した FilesDropped プロシージャを作ります。 ファイルがドロップされた時のイベントハンドラです。 先に、ドロップされたファイル名が分かるように、フォームに label を 1つ置いて下さい。

procedure TForm1.FilesDropped(var Msg1:TWMDropFiles);
var
  FNameSize: UINT;
  Count    : UINT;
  FileName : array[0..255] of Char;
  FileName1: string;
begin
  FileName1 := '';
  try
    Count := DragQueryFile(Msg1.Drop, $FFFFFFFF, nil, 0 );// A
    if Count > 1 then Abort;                              // B
    FNameSize := DragQueryFile(Msg1.Drop, 0, nil, 0) + 1; // C
    DragQueryFile(Msg1.Drop, 0, FileName, FNameSize);     // D
    Filename1 := StrPas(FileName);                        // E
  finally
    DragFinish(Msg1.Drop);                                // F
  end;
  if FileName1 = '' then Exit;
  label1.Caption := Filename1;
end; 

A :ファイル数取得 B :2つ以上なら例外で終了 C :ファイル名長さ取得 D :ファイル名取得 E :パスカル形式に変換 F :メモリ解放
 UINT型は、Unsigned Integer の意味で、C言語と API でプログラムを書く時に 使う型だそうです。Delphi では、UINT 型は LongWord 型と定めているので、 問題なく通じるとのことです。
 TWMDropFiles については、以下のように定義されています。

  TWMDropFiles = packed record
    Msg: Cardinal;
    Drop: THANDLE;
    Unused: Longint;
    Result: Longint;
  end;
 上記の Drop フィールドだけ使います。
 DragQueryFile は3回使う必要があります。
1回目は、ドラッグされたファイル数を取得するために。
2回目は、ファイル名の長さを取得するために。「+1」してるのは、 ヌルで終わるファイル名を受け取るため、ヌル用のバイトが必要だからです。
3回目は、実際にファイル名を受け取るために。
 Win32APIヘルプには、以下の様に定義されています。

UINT DragQueryFile(
    HDROP  hDrop,    // handle to structure for dropped files
    UINT   iFile,    // index of file to query
    LPTSTR lpszFile, // buffer for returned filename
    UINT   cch       // size of buffer for filename
   );
(太字は求められるもの)
戻り値hDropiFilelpszFilecch
ファイル数Msg1.Drop$FFFFFFFFnil0
ファイル名長さ
(ヌルなし)
Msg1.Drop+1番目のファイルnil0
(使わない)Msg1.Drop+1番目のファイルファイル名ファイル名長さ
(ヌル込み)
 finally 部の DragFinish 関数は、ファイル名をアプリケーションに 転送するために Windows が割り当てたメモリを解放する必要があるためです。

5、アプリケーションの終了時には、ドロップの受付終了を Windows に登録するため、 再び、DragAcceptFiles を、今度は FALSE で登録します。

procedure TForm1.FormDestroy(Sender: TObject);
begin
    :
  DragAcceptFiles(Handle, FALSE);
    :
end;
 これで、ウィンドウにファイルを1つD&Dすると、ラベルにファイル名が 表示されるはずです。(^_^)
 自己流の解釈なので、まだ誤りがあるかも知れません。 簡単に思えるファイルのドラッグ&ドロップが、 これだけの手順を踏まないと実現できない事に驚き、 C言語と Win32API でプログラムを組める人達を改めて尊敬しました。 普段 Delphi に甘やかされて、だらだらとプログラムを作っている事を 反省する次第です。もし間違いがありましたら掲示板にて御一報下さい。


ホームに戻る














inserted by FC2 system