ホーム >プログラム >Delphi 6 ローテクTips >Unicode対応のドラッグアンドドロップ

上記サイトのコードをUnicode対応のAPIに書き換えて、ついでに複数ファイルの受け入れを可能にしてみました。


  private
    procedure WMDropFiles(var Msg: TWMDROPFILES); message WM_DROPFILES;
implementation
uses
  ShellAPI;
procedure TForm1.FormCreate(Sender: TObject);
begin
  //ドラッグアンドドロップの受け入れ許可
  DragAcceptFiles(Handle, True);
end;

//ドラッグアンドドロップ
procedure TForm1.WMDropFiles(var Msg: TWMDROPFILES);
var
  i, li_Buff: Integer;
  ls_PWFile:  PWideChar;
  lsl_List:   TStrings;
begin
  DragAcceptFiles(Handle, False);  //ドロップの処理中はドロップを受け付けない

  lsl_List := TStringList.Create;
  try
    try
      //ドロップされたファイルの数だけ処理を行う
      for i := 0 to DragQueryFileW(Msg.Drop, $FFFFFFFF, nil, 0) - 1 do begin
        li_Buff   := DragQueryFileW(Msg.Drop, i, nil, 0);
        ls_PWFile := AllocMem((li_Buff + 1) * 2);
        try
          DragQueryFileW(Msg.Drop, i, ls_PWFile, li_Buff + 1);
          lsl_List.Add(gfnsWideToAnsiEx(WideString(ls_PWFile)));
        finally
          FreeMem(ls_PWFile);
        end;
      end;
    finally
      DragFinish(Msg.Drop);  //ドロップハンドルを解放
    end;

    //-------------------------------------------------
    //ここにリストを使った何らかの処理
    //リストを使うときはgfnsAnsiToWideEx関数で戻してから使う
    //-------------------------------------------------

  finally
    lsl_List.Free;
  end;

  DragAcceptFiles(Handle, True);  //処理が終わったのでドロップの受け入れを許可
end;

UnicodeとStringの可逆変換関数を利用してリストに登録しているので分かりにくいのですが、これをやらないとせっかくUnicode対応のドラッグアンドドロップにした意味がなくなるので。


ところで。
ドラッグアンドドロップ関連のAPIにDragQueryPointというのもあります。ドロップされた時のマウスの位置を取得するAPIなのですが、これを使えばフォーム上のドロップされたコントロールも所得できるのではなかろうかと思うわけです。
例えばフォーム上にある複数のEditにドロップした場合、それぞれのEditで別の処理をしたい場合などに使えるのではなかろうかということです。

//ドラッグアンドドロップ
procedure TForm1.WMDropFiles(var Msg: TWMDROPFILES);
  function lfnrc_OriginRect(Control: TControl): TRect;
  //コントロールのBoundsRectをスクリーン座標で返す
  begin
    with Control do begin
      if (Parent = nil) or (Parent.Handle = Application.Handle) then begin
        Result := BoundsRect;
      end else begin
        Result := Rect(
            Parent.ClientToScreen(Point(Left, Top)),
            Parent.ClientToscreen(Point(Left + Width, Top + Height))
        );
      end;
    end;
  end;
  function lfnrc_ClientOriginRect(Control: TControl): TRect;
  //コントロールのClientRectをスクリーン座標で返す
  begin
    with Control do begin
      Result := Rect(
          ClientToScreen(Point(0, 0)),
          ClientToscreen(Point(ClientWidth, ClientHeight))
      );
    end;
  end;

  function lfnctl_ControlGet(ptPos: TPoint; WinControl: TWinControl): TControl;
  //WinControl上でptPos(スクリーン座標)上にある一番手前のコントロールを返す
  var
    i: Integer;
  begin
    Result := nil;
    with WinControl do begin
      //WinControlの領域内か?
      if (PtInRect(lfnrc_OriginRect(WinControl), ptPos)) then begin
        //クライアント領域内か?
        if (PtInRect(lfnrc_ClientOriginRect(WinControl), ptPos)) then begin
          //クライアント領域内
          for i := ControlCount -1 downto 0 do begin
            if (Controls[i] is TWinControl) then begin
              //WinControlの場合は子コントロールがあるかもしれないので再起呼び出しを行う
              Result := lfnctl_ControlGet(ptPos, TWinControl(Controls[i]));
              //Resultがnilでないということは該当するコントロールがあった
              if (Result <> nil) then begin
                Break;
              end;
            end else begin
              if (PtInRect(lfnrc_ClientOriginRect(Controls[i]), ptPos)) then begin
                Result := Controls[i];
                Break;
              end;
            end;
          end;
          if (Result = nil) then Result := winControl;
        end else begin
          //クライアント領域外だけどWinControl領域内(例えばタイトルバーとか)
          Result := WinControl;
        end;
      end;
    end;
  end;
var
  i, li_Buff:    Integer;
  ls_PWFile:     PWideChar;
  lsl_List:      TStrings;
  l_DropControl: TControl;
  lpt_DropPos:   TPoint;
begin
  DragAcceptFiles(Handle, False);

  DragQueryPoint(Msg.Drop, lpt_DropPos);  //lpt_DropPosはフォームのクライアント座標
  //ドロップされたコントロールを取得
  l_DropControl := lfnctl_ControlGet(Self.ClientToScreen(lpt_DropPos), Self);
  if (l_DropControl <> Self) then begin
    //フォームのクライアント座標のlpt_DropPosをl_DropControlのクライアント座標に変換
    lpt_DropPos := l_DropControl.ScreenToClient(Self.ClientToScreen(lpt_DropPos));
  end;

  //ドロップされたファイルをリストに取得
  lsl_List := TStringList.Create;
  try
    try
      //ドロップされたファイルの数だけ処理を行う
      for i := 0 to DragQueryFileW(Msg.Drop, $FFFFFFFF, nil, 0) - 1 do begin
        li_Buff   := DragQueryFileW(Msg.Drop, i, nil, 0);
        ls_PWFile := AllocMem((li_Buff + 1) * 2);
        try
          DragQueryFileW(Msg.Drop, i, ls_PWFile, li_Buff + 1);
          //オリジナルの変換関数を使ってWideStringをリストに持たせる
          lsl_List.Add(gfnsWideToAnsiEx(WideString(ls_PWFile)));
        finally
          FreeMem(ls_PWFile);
        end;
      end;
    finally
      DragFinish(Msg.Drop);  //ドロップハンドルを解放
    end;

    //--------------------------------------------------
    //ここにリストやコントロールなどを使った何らかの処理
    //リストを使うときはgfnsAnsiToWideEx関数で戻してから使う

    //--------------------------------------------------

  finally
    lsl_List.Free;
  end;

  DragAcceptFiles(Handle, True);  //処理が終わったのでドロップの受け入れを許可
end;

l_DropControl には TPanel や TListBox 等の TWinControl だけでなく TLabel や TImage 等のウィンドウハンドルを持たないコントロールも返ります。
また lpt_DropPos には Form1 のクライアント座標ではなく、ドロップされたコントロールのクライアント座標での値が入ります。