Как внедрить Drag & Drop из Outlook Mail или Thunderbird в форму Delphi? - PullRequest
7 голосов
/ 21 января 2011

Кто-нибудь уже реализовал перетаскивание сообщений электронной почты из Outlook и / или Thunderbird ( отныне "OT" ) в форму Delphi.

Мне нужно дать пользователюспособ хранить важные электронные письма в моей базе данных приложения без написания OT-плагинов.В настоящее время они используют эту технику:

  1. из OT они нажимают на электронную почту,
  2. сохранить как ...
  3. сохранить на рабочем столе или во временной папке,
  4. перетащите сохраненный файл в форму Delphi.

Хотя после изменения я хочу сделать:

  1. из OT они нажимают на электронное письмо,
  2. перетащите сохраненный файл в форму Delphi.

Так что в основном я реализовал перетаскивание из проводника.Мне нужен дополнительный слой, который позволяет моему приложению первоначально видеть электронную почту на OT как обычный файл, поэтому я могу перетащить из OT, как если бы это было обычное окно проводника Windows.

Примечание: мне не нужноподдерживать все версии OT.Я могу согласиться не поддерживать Outlook 2003 (например), но не 2010. Поэтому, если метод не будет работать автоматически для всех версий OT, я предпочту ту, которая работает с самой последней версией.

Последнее замечание:Очевидно, в любом случае меня интересует только перетаскивание писем (а не элементов календаря Outlook, например).Идея будет также перетаскивать и удалять приложения.Но это может быть дополнительным улучшением в будущем.

Ответы [ 2 ]

10 голосов
/ 21 января 2011

Прежде всего, если вы можете найти готовую библиотеку, которая делает это из коробки (например, предложенную ldsandon ), используйте ее, потому что все это выполняется вручную больно и разочаровывает. Временами документация неполная и может содержать ошибки: в конечном итоге вы будете делать что-то методом проб и ошибок, и Google не спасет вас, потому что не многие люди копаются в глубинах Ole drag-and-drop, и большинство из них который, вероятно, будет использовать готовый код.

Как это сделать на простом Паскале

Теоретически API, который используется для того, чтобы приложение обрабатывало отбрасывание OLE, очень прост. Все, что вам нужно сделать, это предоставить реализацию интерфейса 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 потоков ненулевого размера.

7 голосов
/ 21 января 2011

Вы должны использовать OLE Drag & Drop , но тогда вы должны иметь возможность обрабатывать полученные данные, потому что каждое приложение может хранить данные в своем собственном формате. Хорошую реализацию OLE Drag & Drop можно найти здесь

Outlook Express и Thunderbird должны передавать ваши данные в формате RFC2822 или чем-то подобным, Outlook, вероятно, будет передавать данные в своем собственном формате сообщений, это должно быть задокументировано как часть программы Microsoft Open Specification

...