【问题标题】:How to implement Drag & Drop from outlook mail or thunderbird to a delphi form?如何实现从 Outlook 邮件或 Thunderbird 拖放到 delphi 表单?
【发布时间】:2011-06-13 00:18:37
【问题描述】:

是否有人已经实现了将电子邮件从 Outlook 和/或 Thunderbird(从现在开始为“OT”)拖放到 Delphi 表单。

我需要为用户提供一种无需编写 OT 插件即可将重要电子邮件存储在我的应用程序数据库中的方法。目前他们使用这种技术:

  1. 他们从 OT 点击电子邮件,
  2. 另存为...
  3. 保存在桌面或临时文件夹中,
  4. 将保存的文件拖放到 Delphi 表单中。

修改后我想做的:

  1. 他们从 OT 点击电子邮件,
  2. 将保存的文件拖放到 Delphi 表单中。

所以基本上我实现了从资源管理器拖放。我需要一个额外的层,允许我的应用程序将最初在 OT 上的电子邮件视为普通文件,这样我就可以从 OT 中拖动它,就像它是普通的 Windows 资源管理器窗口一样。

注意:我不需要支持所有的 OT 版本。我可以接受不支持 Outlook 2003(例如)但不支持 2010。因此,如果该技术无法自动适用于所有 OT 版本,我将更喜欢与最新版本一起使用的技术。

最后一点:很明显,我只对拖放电子邮件感兴趣(例如,对 Outlook 日历项目不感兴趣)。一个想法也是拖放附件。但这可能是未来的额外改进。

【问题讨论】:

    标签: delphi email drag-and-drop outlook thunderbird


    【解决方案1】:

    首先,如果你能找到一个开箱即用的现成库(如 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;
    

    DragEnterDragOverDragLeave 的实现是微不足道的,考虑到我这样做是为了实验:我会接受一切

    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 个非零大小的流。

    【讨论】:

    • +1 非常感谢您的这篇文章。我感谢你分享你的知识的态度。这对所有社区都是一个加分项。这表示我将选择 lsandon 答案作为已接受的答案,因为“看起来确实”它会轻松解决我的问题。无论如何,我会尝试一下,以防万一我会回到这里并改变我对这个决定的看法。
    • 不确定您阅读的 MSDN 文档中说 lindex 仅作为 -1 有效,但这可能是不同的格式。来自blogs.msdn.com/b/oldnewthing/archive/2008/03/18/8080183.aspx,“文件内容剪贴板格式使用 lindex 作为从零开始的索引,它选择调用者正在谈论的虚拟文件。如果我们只有一个虚拟文件,它的索引为零。”
    【解决方案2】:

    您必须使用OLE Drag&Drop,但是您必须能够处理收到的数据,因为每个应用程序都可以以自己的格式存储数据。你可以找到一个不错的 OLE Drag&Drop 的 Delphi 实现 here

    Outlook Express 和 Thunderbird 应该以 RFC2822 格式或类似格式传输您的数据,Outlook 可能会以其自己的消息格式传输数据,它应该已被记录为 Microsoft Open Specification program 的一部分

    【讨论】:

    • VCL! (非常酷的链接)我刚刚发明的一个笑话,可能是重复的,现在很难发明新的笑话......非常感谢,图书馆似乎有我需要的一切,有一个 OutlookDemo 示例应用程序。我会努力并希望幸运。
    • 我补充一点:不支持 Office 64 位,直到我们拥有 64 位编译器。无论如何,目前(2011 年 1 月)这不是一个大问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-06
    • 2016-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多