How can I allow a form to accept file dropping without handling Windows messages?

前端 未结 5 1774
没有蜡笔的小新
没有蜡笔的小新 2020-11-29 03:54

In Delphi XE can I allow my form to accept file \'drag and drop\' but without having to handle bare windows messages?

相关标签:
5条回答
  • 2020-11-29 04:32

    You don't need to handle messages to implement this. You just need to implement IDropTarget and call RegisterDragDrop/RevokeDragDrop. It's really very very simple. You can actually implement IDropTarget in your form code but I prefer to do it in a helper class that looks like this:

    uses
      Winapi.Windows,
      Winapi.ActiveX,
      Winapi.ShellAPI,
      System.StrUtils,
      Vcl.Forms;
    
    type
      IDragDrop = interface
        function DropAllowed(const FileNames: array of string): Boolean;
        procedure Drop(const FileNames: array of string);
      end;
    
      TDropTarget = class(TObject, IInterface, IDropTarget)
      private
        // IInterface
        function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
        function _AddRef: Integer; stdcall;
        function _Release: Integer; stdcall;
      private
        // IDropTarget
        FHandle: HWND;
        FDragDrop: IDragDrop;
        FDropAllowed: Boolean;
        procedure GetFileNames(const dataObj: IDataObject; var FileNames: TArray<string>);
        procedure SetEffect(var dwEffect: Integer);
        function DragEnter(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult; stdcall;
        function DragOver(grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall;
        function DragLeave: HResult; stdcall;
        function Drop(const dataObj: IDataObject; grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall;
      public
        constructor Create(AHandle: HWND; const ADragDrop: IDragDrop);
        destructor Destroy; override;
      end;
    
    { TDropTarget }
    
    constructor TDropTarget.Create(AHandle: HWND; const ADragDrop: IDragDrop);
    begin
      inherited Create;
      FHandle := AHandle;
      FDragDrop := ADragDrop;
      RegisterDragDrop(FHandle, Self)
    end;
    
    destructor TDropTarget.Destroy;
    begin
      RevokeDragDrop(FHandle);
      inherited;
    end;
    
    function TDropTarget.QueryInterface(const IID: TGUID; out Obj): HResult;
    begin
      if GetInterface(IID, Obj) then begin
        Result := S_OK;
      end else begin
        Result := E_NOINTERFACE;
      end;
    end;
    
    function TDropTarget._AddRef: Integer;
    begin
      Result := -1;
    end;
    
    function TDropTarget._Release: Integer;
    begin
      Result := -1;
    end;
    
    procedure TDropTarget.GetFileNames(const dataObj: IDataObject; var FileNames: TArray<string>);
    var
      i: Integer;
      formatetcIn: TFormatEtc;
      medium: TStgMedium;
      dropHandle: HDROP;
    begin
      FileNames := nil;
      formatetcIn.cfFormat := CF_HDROP;
      formatetcIn.ptd := nil;
      formatetcIn.dwAspect := DVASPECT_CONTENT;
      formatetcIn.lindex := -1;
      formatetcIn.tymed := TYMED_HGLOBAL;
      if dataObj.GetData(formatetcIn, medium)=S_OK then begin
        (* This cast needed because HDROP is incorrectly declared as Longint in ShellAPI.pas.  It should be declared as THandle
           which is an unsigned integer.  Without this fix the routine fails in top-down memory allocation scenarios. *)
        dropHandle := HDROP(medium.hGlobal);
        SetLength(FileNames, DragQueryFile(dropHandle, $FFFFFFFF, nil, 0));
        for i := 0 to high(FileNames) do begin
          SetLength(FileNames[i], DragQueryFile(dropHandle, i, nil, 0));
          DragQueryFile(dropHandle, i, @FileNames[i][1], Length(FileNames[i])+1);
        end;
      end;
    end;
    
    procedure TDropTarget.SetEffect(var dwEffect: Integer);
    begin
      if FDropAllowed then begin
        dwEffect := DROPEFFECT_COPY;
      end else begin
        dwEffect := DROPEFFECT_NONE;
      end;
    end;
    
    function TDropTarget.DragEnter(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
    var
      FileNames: TArray<string>;
    begin
      Result := S_OK;
      Try
        GetFileNames(dataObj, FileNames);
        FDropAllowed := (Length(FileNames)>0) and FDragDrop.DropAllowed(FileNames);
        SetEffect(dwEffect);
      Except
        Result := E_UNEXPECTED;
      End;
    end;
    
    function TDropTarget.DragLeave: HResult;
    begin
      Result := S_OK;
    end;
    
    function TDropTarget.DragOver(grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
    begin
      Result := S_OK;
      Try
        SetEffect(dwEffect);
      Except
        Result := E_UNEXPECTED;
      End;
    end;
    
    function TDropTarget.Drop(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
    var
      FileNames: TArray<string>;
    begin
      Result := S_OK;
      Try
        GetFileNames(dataObj, FileNames);
        if Length(FileNames)>0 then begin
          FDragDrop.Drop(FileNames);
        end;
      Except
        Application.HandleException(Self);
      End;
    end;
    

    The idea here is to wrap up the complexity of the Windows IDropTarget in TDropTarget. All you need to do is to implement IDragDrop which is much simpler. Anyway, I think this should get you going.

    Create the drop target object from your control's CreateWnd. Destroy it in the DestroyWnd method. That point is important because VCL window re-creation means that a control can have its window handle destroyed and re-created during its lifetime.

    Note that reference counting on TDropTarget is suppressed. That is because when RegisterDragDrop is called it increments the reference count. This creates a circular reference and this code to suppress reference counting breaks that. This means that you would use this class through a class variable rather than an interface variable, in order to avoid leaking.

    The usage would look something like this:

    type
      TMainForm = class(TForm, IDragDrop)
        ....
      private
        FDropTarget: TDropTarget;
    
        // implement IDragDrop
        function DropAllowed(const FileNames: array of string): Boolean;
        procedure Drop(const FileNames: array of string);
      protected
        procedure CreateWindowHandle; override;
        procedure DestroyWindowHandle; override;
      end;
    
    ....
    
    procedure TMainForm.CreateWindowHandle;
    begin
      inherited;
      FDropTarget := TDropTarget.Create(WindowHandle, Self);
    end;
    
    procedure TMainForm.DestroyWindowHandle;
    begin
      FreeAndNil(FDropTarget);
      inherited;
    end;
    
    function TMainForm.DropAllowed(const FileNames: array of string): Boolean;
    begin
      Result := True;
    end;
    
    procedure TMainForm.Drop(const FileNames: array of string);
    begin
      ; // do something with the file names
    end;
    

    Here I am using a form as the drop target. But you could use any other windowed control in a similar fashion.

    0 讨论(0)
  • 2020-11-29 04:34

    No, unless you are about to peruse some custom TForm descendant which have this functionality built-in already.

    0 讨论(0)
  • 2020-11-29 04:41

    I used David Heffernan's solution as base for my test application and got 'Invalid pointer operation' on application close. The solution for that problem was to change the TDropTarget.Create by adding '_Release;'

    constructor TDropTarget.Create(AHandle: HWND; const ADragDrop: IDragDrop);
    begin
      inherited Create;
      FHandle := AHandle;
      FDragDrop := ADragDrop;
      RegisterDragDrop(FHandle, Self);
      _Release;
    end;
    

    A discussion about this problem you can see on Embarcadero forum.

    0 讨论(0)
  • 2020-11-29 04:42

    You have to either write code yourself, or install a 3rd party product like DropMaster, which lets you do drag and drop in much older Delphi versions as well.

    --jeroen

    0 讨论(0)
  • 2020-11-29 04:56

    If you don't like pure WinAPI, then you can use components. Drag and Drop Component Suite is free with sources.

    0 讨论(0)
提交回复
热议问题