[Lazarus] GTK2: Drag files to other applications (to the file manager, Thunar, Nautilus, etc)

Bernd prof7bit at gmail.com
Tue Aug 21 21:08:31 CEST 2012


Intermediate result of first experiments on windows. May the next
person who googles for this rare problem (everybody knows how to
receive files but only few people have ever implemented an IDataObject
 themselves and then posted the results) find this post in which it is
all condensed into only 200 lines of a readable programming language:

unit win32dragdrop;

{$mode objfpc}{$H+}

interface

procedure DoVeryComplicatedStuffThatIsNotWellDocumented;


implementation
uses
  Classes, Windows, ActiveX, shlobj;

const
  MyFileDragFormat: FORMATETC = (
    CfFormat : CF_HDROP;
    Ptd      : nil;
    dwAspect : DVASPECT_CONTENT;
    lindex   : -1;
    tymed    : TYMED_HGLOBAL;
  );

type

  { TDropSource }

  TDropSource = class(TInterfacedObject, IDropSource)
    function QueryContinueDrag(fEscapePressed: BOOL; grfKeyState:
DWORD): HResult; StdCall;
    function GiveFeedback(dwEffect: DWORD): HResult; StdCall;
  end;

  { TDataObject }

  TDataObject = class(TInterfacedObject, IDataObject)
    function GetData(const formatetcIn: FORMATETC; out medium:
STGMEDIUM): HRESULT; STDCALL;
    function GetDataHere(const pformatetc: FormatETC; out medium:
STGMEDIUM): HRESULT; STDCALL;
    function QueryGetData(const pformatetc: FORMATETC): HRESULT; STDCALL;
    function GetCanonicalFormatEtc(const pformatetcIn: FORMATETC; out
pformatetcOut: FORMATETC): HResult; STDCALl;
    function SetData(const pformatetc: FORMATETC; const medium:
STGMEDIUM; FRelease: BOOL): HRESULT; StdCall;
    function EnumFormatEtc(dwDirection: DWord; out enumformatetcpara:
IENUMFORMATETC): HRESULT; StdCall;
    function DAdvise(const formatetc: FORMATETC; advf: DWORD; const
AdvSink: IAdviseSink; out dwConnection: DWORD): HRESULT; StdCall;
    function DUnadvise(dwconnection: DWord): HRESULT; StdCall;
    function EnumDAdvise(out enumAdvise: IEnumStatData): HResult; StdCall;
  private
    function HaveThisFormat(const f: TFORMATETC): Boolean;
  end;

{$note SHCreateStdEnumFmtEtc() definition in shlobj is wrong, report this bug}
function SHCreateStdEnumFmtEtc(cfmt:UINT; afmt: PFORMATETC; var
ppenumFormatEtc:IEnumFORMATETC):HRESULT;StdCall;external 'shell32'
name 'SHCreateStdEnumFmtEtc';

procedure DoVeryComplicatedStuffThatIsNotWellDocumented;
var
  DataObject: IDataObject;
  DropSource: IDropSource;
  DWEffect: DWord;
begin
  DataObject := TDataObject.Create;
  DropSource := TDropSource.Create;
  DoDragDrop(DataObject, DropSource, DROPEFFECT_COPY, @DWEffect);
  WriteLn(DWEffect);
end;

{ TDataObject }


function TDataObject.GetData(const formatetcIn: FORMATETC; out medium:
STGMEDIUM): HRESULT; STDCALL;
var
  FileList: TStringList;
  FileName: String;
  sFileList: WideString;
  BufLen: PtrInt;
  hgDropFiles: THANDLE;
  pgDropFiles: PDROPFILES;

begin
  // This method may be called multiple times amd also even when no
  // drop happens at all because I am using CF_HDROP which means: files
  // exist in the file system and so windows thinks it is ok to access
  // the dragged data immediately after dragging has begun!

  if HaveThisFormat(formatetcIn) then begin

    // for this list we would normally ask the FileDragSource component
    // once its all completely implemented but for now I just quickly
    // make up a list of filenames myself.
    FileList := TStringList.Create;
    FileList.Append('c:\dummy.txt');
    FileList.Append('c:\dummy2.txt');

    // First we need a widestring #0 sepatated and #0#0 at the end.
    for FileName in FileList do begin
      sFileList += FileName + #0;
    end;
    sFileList += #0;
    FileList.Free;

    // now we need to allocate memory for the DROPFILES structure
    // we need room for that structure plus the above widestring
    BufLen := SizeOf(DROPFILES) + 2*Length(sFileList); //widestring!
    hgDropFiles := GlobalAlloc(GMEM_MOVEABLE or GMEM_DDESHARE or
GMEM_ZEROINIT, BufLen);

    // populate the DROPFILES structure,
    // it has the string of filenames appended directly at the end
    pgDropFiles := GlobalLock(hgDropFiles);
    pgDropFiles^.pFiles := SizeOf(DROPFILES); // offset of the file list
    pgDropFiles^.fWide := True; // contains widestring!
    Move(sFileList[1], pgDropFiles[1], 2*Length(sFileList)); // widestring!
    GlobalUnlock(hgDropFiles);

    // populate the STGMEDIUM structore;
    medium.Tymed := TYMED_HGLOBAL;
    medium.HGLOBAL := hgDropFiles;
    medium.PUnkForRelease := nil;

    Result := S_OK;
  end
  else
    Result := DV_E_FORMATETC;
end;

function TDataObject.GetDataHere(const pformatetc: FormatETC; out
medium: STGMEDIUM): HRESULT; STDCALL;
begin
end;

function TDataObject.QueryGetData(const pformatetc: FORMATETC):
HRESULT; STDCALL;
begin
  if HaveThisFormat(pformatetc) then
    Result := S_OK
  else
    Result := DV_E_FORMATETC;
end;

function TDataObject.GetCanonicalFormatEtc(const pformatetcIn:
FORMATETC; out pformatetcOut: FORMATETC): HResult; STDCALl;
begin
end;

function TDataObject.SetData(const pformatetc: FORMATETC; const
medium: STGMEDIUM; FRelease: BOOL): HRESULT; StdCall;
begin
end;

function TDataObject.EnumFormatEtc(dwDirection: DWord; out
enumformatetcpara: IENUMFORMATETC): HRESULT; StdCall;
var
  E: IEnumFORMATETC;
begin
  if dwDirection = DATADIR_GET then
    Result := SHCreateStdEnumFmtEtc(1, @MyFileDragFormat, enumformatetcpara)
  else
    Result := E_NOTIMPL;
end;

function TDataObject.DAdvise(const formatetc: FORMATETC; advf: DWORD;
const AdvSink: IAdviseSink; out dwConnection: DWORD): HRESULT;
StdCall;
begin
  Result := OLE_E_ADVISENOTSUPPORTED;
end;

function TDataObject.DUnadvise(dwconnection: DWord): HRESULT; StdCall;
begin
  Result := OLE_E_ADVISENOTSUPPORTED;
end;

function TDataObject.EnumDAdvise(out enumAdvise: IEnumStatData):
HResult; StdCall;
begin
  Result := OLE_E_ADVISENOTSUPPORTED;
end;

function TDataObject.HaveThisFormat(const f: TFORMATETC): Boolean;
begin
  if (f.tymed = MyFileDragFormat.tymed)
  and (f.CfFormat = MyFileDragFormat.CfFormat)
  and (f.dwAspect = MyFileDragFormat.dwAspect) then
    Result := True
  else
    Result := False;
end;

{ TDragSource }

function TDropSource.QueryContinueDrag(fEscapePressed: BOOL;
grfKeyState: DWORD): HResult; StdCall;
begin
  // if the Escape key has been pressed since the last call, cancel the drop
  if fEscapePressed = True then
    exit(DRAGDROP_S_CANCEL);

  // if the LeftMouse button has been released, then do the drop!
  if (grfKeyState and MK_LBUTTON) = 0 then
    exit(DRAGDROP_S_DROP);

  // continue with the drag-drop
  Result := S_OK;
end;

function TDropSource.GiveFeedback(dwEffect: DWORD): HResult; StdCall;
begin
  Result := DRAGDROP_S_USEDEFAULTCURSORS;
end;

initialization
  OleInitialize(nil);
finalization
  OleUninitialize();
end.




More information about the Lazarus mailing list