Does anyone already implemented Drag & Drop of email messages from Outlook and/or Thunderbird (from now on "OT") to a Delphi form.
I need to give the user a way to store important emails in my application database without writing OT plugins. Currently they use this technique:
While after the modification I want to do:
So basically I implemented drag & drop from explorer. I need an extra layer that allows my application to see the email originally on OT as a normal file, so I can drag from OT as if it was a normal windows explorer window.
Note: I don't need to support all OT versions. I can accept not to support Outlook 2003 (for example) but not 2010. So in case the technique will not work automatically for all OT versions I will prefer the one that works with the latest.
Final note: It is obvious anyway I am interested only in dragging & dropping emails (and not Outlook Calendar items, for example). An idea would be dragging and Dropping attachments too. But this could be an extra improvement for the future.
First of all, if you can find a ready made library that does this out of the box (like the one suggested by ldsandon) use it, because doing all of this by hand is painful and frustrating. The documentation is at times incomplete and might contain bugs: you'll end up doing stuff by trial and error and Google will not save you because not a lot of people delve into the depths of Ole drag-and-drop, and most of those that do will probably use ready-made code.
Theoretically the API that's used to make your application handle OLE drops is very simple. All you need to do is provide a implementation of the IDropTarget
interface that does what you need and call RegisterDragDrop
providing the handle for your application's window and the interface.
Here's how 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
is trivial, considerring I'm doing this for an experiment: I'll 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 the "Drop", looks for some CF_FILECONTENTS, opens it up as TYMED_ISTORAGE, drops every single stream in that storage to a file in C:\Temp\<stream_name>.bin
; I tried this with Delphi 2010 and Outlook 2007, it works all right: Opening up those saved files (lots of them!) I can find everything from the email message, in unexpected ways. I'm sure there's documentation somewhere that explains exactly what every one of those files is supposed to contain, but I don't really care about accepting drag-and-dropped files from Outlook so I didn't look to far. Again, ldsandon's link looks promising.
This codes looks fairly short, but that's not the source of the difficulties. The documentation for this was really lacking; I hit road blocks at every corner, starting with this:
F.lindex := 0{-1}; // Documentation says -1, practice says "0"
Msdn's documentation clear says the only valid value for "lindex" is -1: guess what, -1 doesn't work, 0 does!
Then there's this short line of code:
Response := Storage.OpenStream(ST_TAG.pwcsName, nil, STGM_READ or STGM_SHARE_EXCLUSIVE, 0, Stream);
specifically, those two consts:
STGM_READ or STGM_SHARE_EXCLUSIVE
getting that combination was a matter of trial-and-error. I don't like trial and error: Is that the optimal combination of flags for what I want? Will that work on every platform? I don't know...
Then there's the matter of making heads or tail of the actual content received from Outlook. For example the SUBJECT of the email was found in this stream: __substg1.0_800A001F
. The body of the message was found in this stream: __substg1.0_1000001F
. For an simple email message I got 59 streams of non-zero size.