Как можно разрешить форме принимать удаление файлов без обработки сообщений Windows? - PullRequest
17 голосов
/ 04 декабря 2010

В Delphi XE можно ли разрешить моей форме принимать файл с помощью перетаскивания, но без необходимости обрабатывать сообщения без окон?

Ответы [ 5 ]

27 голосов
/ 04 декабря 2010

Вам не нужно обрабатывать сообщения для реализации этого.Вам просто нужно реализовать IDropTarget и позвонить RegisterDragDrop / RevokeDragDrop.Это действительно очень очень просто.На самом деле вы можете реализовать IDropTarget в своем коде формы, но я предпочитаю делать это во вспомогательном классе, который выглядит следующим образом:

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;

Идея в том, чтобы обернуть сложность Windows IDropTargetв TDropTarget.Все, что вам нужно сделать, это реализовать IDragDrop, что намного проще.В любом случае, я думаю, что это должно помочь вам.

Создайте целевой объект перетаскивания из CreateWnd вашего элемента управления.Уничтожьте его методом DestroyWnd.Этот момент важен, потому что пересоздание окна VCL означает, что элемент управления может иметь дескриптор окна, уничтоженный и воссозданный в течение его срока службы.

Обратите внимание, что подсчет ссылок на TDropTarget подавлен.Это потому, что когда вызывается RegisterDragDrop, он увеличивает счетчик ссылок.Это создает циклическую ссылку, и этот код для подавления подсчета ссылок нарушает это.Это означает, что вы будете использовать этот класс через переменную класса, а не через интерфейсную переменную, чтобы избежать утечки.

Использование будет выглядеть примерно так:

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 CreateWnd; override;
    procedure DestroyWnd; override;
  end;

....

procedure TMainForm.CreateWnd;
begin
  inherited;
  FDropTarget := TDropTarget.Create(WindowHandle, Self);
end;

procedure TMainForm.DestroyWnd;
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;

Вот яиспользуя форму в качестве цели отбрасывания.Но вы можете использовать любой другой оконный элемент управления аналогичным образом.

7 голосов
/ 05 декабря 2010

Если вам не нравится чистый WinAPI, вы можете использовать компоненты. Drag and Drop Component Suite бесплатно с источниками.

2 голосов
/ 03 февраля 2014

Я использовал решение Дэвида Хеффернана в качестве основы для моего тестового приложения и получил «Недопустимая операция с указателем» при закрытии приложения. Решением этой проблемы было изменение TDropTarget.Create путем добавления '_Release;'

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

Обсуждение этой проблемы вы можете увидеть на форуме Embarcadero .

2 голосов
/ 04 декабря 2010

Нет, если только вы не собираетесь просматривать какой-либо пользовательский потомок TForm, который уже имеет эту встроенную функциональность.

0 голосов
/ 04 декабря 2010

Вы должны либо написать код самостоятельно, либо установить сторонний продукт, такой как DropMaster , который позволяет перетаскивать и перетаскивать его в гораздо более старые версии Delphi.

...