How to implement Drag & Drop from Outlook or Thunderbird mail to delphi form?

Has anyone already implemented dragging and dropping emails from Outlook and / or Thunderbird ( from now on β€œOT” ) into a Delphi form.

I need to provide the user with a way to store important emails in my application database without writing OT plugins. They currently use this method:

  • from ot they click on the email address
  • save as...
  • save on the desktop or in the temp folder,
  • drag the saved file into the Delphi form.

So far after the modification I want to do:

  • from ot they click on the email address
  • drag the saved file into the Delphi form.

So basically I implemented drag and drop from explorer. I need an extra layer that allows my application to see the original email address in OT as a regular file, so I can drag it from OT as if it were a regular Windows Explorer window.

Note. I do not need to support all versions of OT. I can agree not to support Outlook 2003 (for example), but not 2010. Therefore, if the technique does not work automatically for all versions of OT, I prefer the one that works with the latest.

Final note. Obviously, in any case, I'm only interested in dragging and dropping email messages (and not, for example, Outlook calendar items). The idea is to drag and drop attachments. But this could be an additional improvement for the future.

+7
source share
2 answers

You should use OLE Drag & Drop , but then you should be able to process the received data, since each application can store data in its own format. You can find a great implementation of Delphi OLE Drag & Drop here

Outlook Express and Thunderbird should transfer your data in RFC2822 format or something similar, Outlook will probably transfer the data to its own message format, it should be documented as part of the Microsoft Open Specification Program

+7
source

First of all, if you can find a ready-made library that does this out of the box (for example, suggested by ldsandon ), use it because it all becomes painful and frustrating by hand. The documentation is sometimes incomplete and may contain errors: you will end up working with trial versions and an error, and Google will not save you, because not many people are immersed in the depth of dragging Ole, and most of them who are likely to use ready-made code.

How to do it in regular Pascal

Theoretically, the API that used to make your application process OLE drops is very simple. All you have to do is provide an implementation of the IDropTarget interface that does what you need and call RegisterDragDrop , providing a handle for your application window and interface.

This is what my implementation looks like:

  TDropTargetImp = class(TInterfacedObject, IDropTarget) public 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; end; 

The implementation of DragEnter , DragOver and DragLeave trivial, assuming that I am doing this for an experiment: I will just accept everything:

 function TDropTargetImp.DragEnter(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult; begin dwEffect := DROPEFFECT_COPY; Result := S_OK; end; function TDropTargetImp.DragLeave: HResult; begin Result := S_OK; end; function TDropTargetImp.DragOver(grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult; begin dwEffect := DROPEFFECT_COPY; Result := S_OK; end; 

The real work will be done in TDropTargetImp.Drop .

 function TDropTargetImp.Drop(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult; var iEnum: IEnumFORMATETC; DidRead:LongInt; F: TFormatEtc; STG:STGMEDIUM; Response:Integer; Stream:IStream; Storage: IStorage; EnumStg: IEnumStatStg; ST_TAG: STATSTG; FileStream: TFileStream; Buff:array[0..1023] of Byte; begin if dataObj.EnumFormatEtc(DATADIR_GET, iEnum) = S_OK then begin { while (iEnum.Next(1, F, @DidRead) = S_OK) and (DidRead > 0) do begin GetClipboardFormatName(F.cfFormat, FormatName, SizeOf(FormatName)); ShowMessage(FormatName + ' : ' + IntToHex(F.cfFormat,4) + '; lindex=' + IntToStr(F.lindex)); end; } ZeroMemory(@F, SizeOf(F)); F.cfFormat := $C105; // CF_FILECONTENTS F.ptd := nil; F.dwAspect := DVASPECT_CONTENT; F.lindex := 0{-1}; // Documentation says -1, practice says "0" F.tymed := TYMED_ISTORAGE; Response := dataObj.GetData(F, STG); if Response = S_OK then begin case STG.tymed of TYMED_ISTORAGE: begin Storage := IStorage(STG.stg); if Storage.EnumElements(0, nil, 0, EnumStg) = S_OK then begin while (EnumStg.Next(1, ST_TAG, @DidRead) = S_OK) and (DidRead > 0) do begin if ST_TAG.cbSize > 0 then begin Response := Storage.OpenStream(ST_TAG.pwcsName, nil, STGM_READ or STGM_SHARE_EXCLUSIVE, 0, Stream); if Response = S_OK then begin // Dump the stored stream to a file FileStream := TFileStream.Create('C:\Temp\' + ST_TAG.pwcsName + '.bin', fmCreate); try while (Stream.Read(@Buff, SizeOf(Buff), @DidRead) = S_OK) and (DidRead > 0) do FileStream.Write(Buff, DidRead); finally FileStream.Free; end; end else case Response of STG_E_ACCESSDENIED: ShowMessage('STG_E_ACCESSDENIED'); STG_E_FILENOTFOUND: ShowMessage('STG_E_FILENOTFOUND'); STG_E_INSUFFICIENTMEMORY: ShowMessage('STG_E_INSUFFICIENTMEMORY'); STG_E_INVALIDFLAG: ShowMessage('STG_E_INVALIDFLAG'); STG_E_INVALIDNAME: ShowMessage('STG_E_INVALIDNAME'); STG_E_INVALIDPOINTER: ShowMessage('STG_E_INVALIDPOINTER'); STG_E_INVALIDPARAMETER: ShowMessage('STG_E_INVALIDPARAMETER'); STG_E_REVERTED: ShowMessage('STG_E_REVERTED'); STG_E_TOOMANYOPENFILES: ShowMessage('STG_E_TOOMANYOPENFILES'); else ShowMessage('Err: #' + IntToHex(Response, 4)); end; end; end; end; end else ShowMessage('TYMED?'); end; end else case Response of DV_E_LINDEX: ShowMessage('DV_E_LINDEX'); DV_E_FORMATETC: ShowMessage('DV_E_FORMATETC'); DV_E_TYMED: ShowMessage('DV_E_TYMED'); DV_E_DVASPECT: ShowMessage('DV_E_DVASPECT'); OLE_E_NOTRUNNING: ShowMessage('OLE_E_NOTRUNNING'); STG_E_MEDIUMFULL: ShowMessage('STG_E_MEDIUMFULL'); E_UNEXPECTED: ShowMessage('E_UNEXPECTED'); E_INVALIDARG: ShowMessage('E_INVALIDARG'); E_OUTOFMEMORY: ShowMessage('E_OUTOFMEMORY'); else ShowMessage('Err = ' + IntToStr(Response)); end; end; Result := S_OK; end; 

This code accepts "Drop", searches for some CF_FILECONTENTS, opens it as TYMED_ISTORAGE, transfers each stream in this repository to a file in C:\Temp\<stream_name>.bin ; I tried this with Delphi 2010 and Outlook 2007, it works fine: opening these saved files (there are a lot of them!) I can find everything from the email in an unexpected way. I'm sure there is somewhere documentation that explains what each of these files should contain, but I really don't care about accepting dragged files from Outlook, so I did not look far. Again, the ldsandon link looks promising.

These codes look pretty short, but this is not a source of difficulty. The documentation for this was really not enough; I hit road blocks at every corner, starting with this:

 F.lindex := 0{-1}; // Documentation says -1, practice says "0" 

Msdn document clear says that the only valid value for "lindex" is -1: guess what, -1 doesn't work, 0 does!

Then there is this short line of code:

 Response := Storage.OpenStream(ST_TAG.pwcsName, nil, STGM_READ or STGM_SHARE_EXCLUSIVE, 0, Stream); 

in particular, these two constants:

 STGM_READ or STGM_SHARE_EXCLUSIVE 

getting this combination was a matter of trial and error. I don’t like trial and error: is this the optimal combination of flags for what I want? Will this work on every platform? I dont know...

The question then becomes about making the headers or tails of the actual content obtained from Outlook. For example, a SUBJECT email message was found in this stream: __substg1.0_800A001F . The body of the message was found in this thread: __substg1.0_1000001F . For a simple email message, I received 59 non-zero sized threads.

+9
source

All Articles