首先,如果你能找到一个现成的库,开箱即用(就像ldsandon建议的那样)使用它,因为手工完成所有这些是痛苦和令人沮丧的。文档有时不完整并且可能包含错误:您最终会通过反复试验来完成工作,而 Google 不会拯救您,因为没有多少人深入研究 Ole 拖放的深度,而且其中大部分这样做可能会使用现成的代码。
如何在普通 Pascal 中做到这一点
从理论上讲,用于使您的应用程序处理 OLE 丢弃的 API 非常简单。您需要做的就是提供一个IDropTarget
接口的实现来满足您的需求,并调用RegisterDragDrop
为您的应用程序窗口和接口提供句柄。
这是我的实现的样子:
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;
的实现DragEnter
,DragOver
并且DragLeave
是微不足道的,考虑到我正在做这个实验:我会接受一切:
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;
真正的工作将在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;
此代码接受“Drop”,查找一些 CF_FILECONTENTS,将其打开为 TYMED_ISTORAGE,将该存储中的每个流放到C:\Temp\<stream_name>.bin
; 我在 Delphi 2010 和 Outlook 2007 上尝试过,一切正常:打开那些保存的文件(很多!)我可以从电子邮件中以意想不到的方式找到所有内容。我确信某处有文档准确地解释了每个文件应该包含的内容,但我并不真正关心从 Outlook 接受拖放文件,所以我没有看太远。再次,ldsandon 的链接看起来很有希望。
这段代码看起来很短,但这不是困难的根源。确实缺乏这方面的文档;我在每个角落都遇到了路障,从这个开始:
F.lindex := 0{-1}; // Documentation says -1, practice says "0"
Msdn 的文档清楚地表明“lindex”的唯一有效值是 -1:猜猜看,-1 不起作用,0 起作用!
然后是这行简短的代码:
Response := Storage.OpenStream(ST_TAG.pwcsName, nil, STGM_READ or STGM_SHARE_EXCLUSIVE, 0, Stream);
具体来说,这两个常量:
STGM_READ or STGM_SHARE_EXCLUSIVE
获得这种组合是一个反复试验的问题。我不喜欢反复试验:这是我想要的标志的最佳组合吗?这适用于每个平台吗?我不知道...
然后是从 Outlook 接收到的实际内容的头部或尾部的问题。例如,在此流中找到电子邮件的主题:__substg1.0_800A001F
。在此流中找到消息的正文:__substg1.0_1000001F
。对于一封简单的电子邮件,我得到了 59 个非零大小的流。