首页 > 技术文章 > 一个能接受外部拖拽的控件(文字或文件)

lzl_17948876 2014-08-21 11:06 原文

恩....也是这2天写的一个小东西的需求, 可以拖拽外部文本文件, 或者选择的一段文本到Memo里显示

查了一下资料, 主要从2个方面实现:

  1.拖拽文件实现WM_DROPFILES就可以了

  2.拖拽文本需要实现IDropTarget接口

 

针对这个功能, 重新封装了一个Memo出来:

  TDropMemo = class(TMemo, IUnknown, IDropTarget)
  private
    FDropAccept: Boolean;
    FDTDropAccept: HResult;
    FFE: TFormatEtc;
    FRefCount: Integer;
  protected
    procedure WMDropFiles(var Msg: TWMDropFiles); message WM_DROPFILES;
    procedure SetDropAccept(const Value: Boolean);
    {IUnknown}
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    {IDropTarget}
    function DragEnter(const dataObj: IDataObject; grfKeyState: Longint;
      pt: TPoint; var dwEffect: Longint): 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
    property DropAccept: Boolean read FDropAccept write SetDropAccept;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

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

{ TDragMemo }

constructor TDropMemo.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FRefCount := 0;
end;

destructor TDropMemo.Destroy;
begin
  inherited;
end;

function TDropMemo.DragEnter(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint;
  var dwEffect: Integer): HResult;
begin
  Result := E_FAIL;
  FDTDropAccept := E_FAIL;

  if not FDropAccept then
    Exit;

  if not Assigned(dataObj) then
    Exit;

  with FFE do
  begin
{$IFDEF UNICODE}
    cfFormat := CF_UNICODETEXT;
{$ELSE}
    cfFormat := CF_TEXT;
{$ENDIF}
    ptd := nil;
    dwAspect := DVASPECT_CONTENT;
    lindex := -1;
    tymed := TYMED_HGLOBAL;
  end;
  FDTDropAccept := dataObj.QueryGetData(FFE);
  Result := FDTDropAccept;
  if not FAILED(Result) then
    dwEffect := DROPEFFECT_COPY
  else
    dwEffect := DROPEFFECT_NONE;
end;

function TDropMemo.DragLeave: HResult;
begin
  Result := S_OK;
end;

function TDropMemo.DragOver(grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
begin
  Result := S_OK;
end;

function TDropMemo.Drop(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint;
  var dwEffect: Integer): HResult;
var
  nMedium: stgMedium;
  nHData: HGLOBAL;
begin
  Result := E_FAIL;

  if FAILED(FDTDropAccept) then
    Exit;

  Result := dataObj.GetData(FFE, nMedium);
  nHData := HGLOBAL(GlobalLock(nMedium.hGlobal));
  try
    SendMessage(Handle, WM_SETTEXT, 0, nHData);
  finally
    GlobalUnlock(nHData);
    GlobalFree(nHData);
  end;
end;

function TDropMemo.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := S_OK
  else
    Result := E_NOINTERFACE;
end;

procedure TDropMemo.SetDropAccept(const Value: Boolean);
begin
  FDropAccept := Value;
  DragAcceptFiles(Handle, FDropAccept);
  if FDropAccept then
    RegisterDragDrop(Handle, Self)
  else
    RevokeDragDrop(Handle);
end;

procedure TDropMemo.WMDropFiles(var Msg: TWMDropFiles);
var
  nBuffer: array[0..255] of Char;
  nCount: Integer;
  nFile: string;
begin
  with Msg do
  begin
    nCount := DragQueryFile(Drop, $FFFFFFFF, nBuffer, 1);
    if nCount = 0 then
      Exit;
    DragQueryFile(Drop, 0, nBuffer, SizeOf(nBuffer));
    nFile := nBuffer;
    DragFinish(Drop);
  end;
  Lines.LoadFromFile(nFile);
end;

function TDropMemo._AddRef: Integer;
begin
  Result := InterLockedDecrement(FRefCount);
  if Result = 0 then
    Destroy;
end;

function TDropMemo._Release: Integer;
begin
  Result := InterLockedIncrement(FRefCount);
end;

 

使用的时候, 通过DropAccept属性控制是否开启过拽支持

 

这个只是支持拖拽到Memo内, 如果想实现拖拽Memo内容到外部, 还需要再实现IDropSource接口, 因为没需求就懒得做了, 哪位有空闲可以一起实现了

 

另外, 从网上找了一个别人封装的拖拽控件, 基本可以支持所有文本编辑控件:

  TDropText = class(TObject, IUnknown, IDropTarget)
  private
    FHandle: THandle;
    FCanDrop: HResult;
    FFE: TFormatEtc;
    FRefCount: Integer;
  protected
    {IUnknown}
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    {IDropTarget}
    function DragEnter(const dataObj: IDataObject; grfKeyState: Longint;
      pt: TPoint; var dwEffect: Longint): 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: THandle);
    destructor Destroy; override;
  end;

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

function TDropText._AddRef: Integer;
begin
  Result := InterLockedDecrement(FRefCount);
  if Result = 0 then
    Destroy;
end;

function TDropText._Release: Integer;
begin
  Result := InterLockedIncrement(FRefCount);
end;

constructor TDropText.Create(AHandle: THandle);
begin
  FRefCount := 0;
  FHandle := AHandle;
  RegisterDragDrop(FHandle, Self);
end;

destructor TDropText.Destroy;
begin
  RevokeDragDrop(FHandle);
  inherited;
end;

function TDropText.DragEnter(const dataObj: IDataObject;
  grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
begin
  Result := E_FAIL;
  FCanDrop := E_FAIL;

  if not Assigned(dataObj) then
    Exit;

  with FFE do
  begin
{$IFDEF UNICODE}
    cfFormat := CF_UNICODETEXT;
{$ELSE}
    cfFormat := CF_TEXT;
{$ENDIF}
    ptd := nil;
    dwAspect := DVASPECT_CONTENT;
    lindex := -1;
    tymed := TYMED_HGLOBAL;
  end;
  FCanDrop := dataObj.QueryGetData(FFE);
  Result := FCanDrop;
  if not FAILED(Result) then
    dwEffect := DROPEFFECT_COPY
  else
    dwEffect := DROPEFFECT_NONE;
end;

function TDropText.DragLeave: HResult;
begin
  Result := S_OK;
end;

function TDropText.DragOver(grfKeyState: Integer; pt: TPoint;
  var dwEffect: Integer): HResult;
begin
  Result := S_OK;
end;

function TDropText.Drop(const dataObj: IDataObject; grfKeyState: Integer;
  pt: TPoint; var dwEffect: Integer): HResult;
var
  nMedium: stgMedium;
  nHData: HGLOBAL;
begin
  Result := E_FAIL;

  if FAILED(FCanDrop) then
    Exit;

  Result := dataObj.GetData(FFE, nMedium);
  nHData := HGLOBAL(GlobalLock(nMedium.hGlobal));
  try
    SendMessage(FHandle, WM_SETTEXT, 0, nHData);
  finally
    GlobalUnlock(nHData);
    GlobalFree(nHData);
  end;
end;

function TDropText.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := S_OK
  else
    Result := E_NOINTERFACE;
end;

调用方式:

   FDragText:= TDropText.Create(Memo1.Handle);

这样就可以让任何拥有文字编辑功能的控件支持文字拖拽的效果了

推荐阅读