Обработка событий MS Outlook
Опубликовано 27.12.2010 г. 23:20
Вернувшись из командировки из республики Тыва (г. Кызыл) решил, что не будет у меня времени доделать новогоднюю утилиту на конкурс webdelphi.ru, так как задумывалось, поэтому отправил в таком виде в каком есть.
В чем суть данного приложения: Если вы используете MS Outlook в качестве почтового клиента, то данное приложение информирует вас о поступлении новых писем. Да, outlook и сам может показывать различные напоминания, но когда эта делает Санта, то как то веселее (: Реализацию с поворотом Санты с использованием WIC мы уже рассматривали в одной из предыдущих статей. Задумывалось, что Санта и все остальное будет отрисовываться с использованием Direct2D и иметь какую то анимацию (Windows Animation Manager), но более чем недельная командировка изменила мои планы (: Данная статья расскажет о том, как вы можете узнать, о получении нового письма в Outlook:
События в Outlook
Поставим перед собой такую задачу: наше приложение должно ожидать запуска или находить уже запущенное приложение MS Outlook, и обрабатывать события прихода новой почты, или события закрытия почтового клиента, чтобы перейти обратно в состояние ожидания запуска. Для реализации нашей идеи создадим соответствующий класс TOutlookEventListener. Сразу приведу описание класса, а потом более подробно о том что, как и почему.TOutlookEventListener = class(TInterfacedObject, IDispatch) strict private outlook : _Application; connectionPoint : IConnectionPoint; cookie : integer; function GetTypeInfoCount(out Count: Integer): HResult; stdcall; function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall; function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall; function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall; public notifyHandle : HWND; constructor Create(); procedure newMailEventAction(); procedure CloseEventAction(); class function IsOutlookRunning():boolean; end;Как мы видим мы создаем объект унаследованный от класса TInterfacedObject поддерживающий интерфейс IDispatch. Для работы нам необходимо импортировать библиотеку типов Outlook_TLB.pas К сожалению она весьма большая, и названия интерфейсов я оставил в том виде, как они есть. Вы можете заметить private-член класса outlook : _Application (при импорте Delphi немного его переименовал, добавив подчеркивание). Интерфейс имеет следующий идентификатор, и описывает объект для работы с приложением Outlook.
IID__Application: TGUID = '{00063001-0000-0000-C000-000000000046}';По хорошему, стоит назвать константу IID_OutlookApplication, а также поменять имя самого интерфейса. Итак, данный член класса будет содержать ссылку на объект запущенного приложения Outlook. Мы не собираемся создавать свою копию приложения, мы будем ждать запуска от пользователя. Для этого по таймеру, например, раз в 20 секунд мы будем проверять, не запущен ли Outlook, используя функцию getActiveOleObject(). Для удобства использования в вызывающей программе, мы реализовали статическую функцию класса IsOutlookRunning, поэтому сам объект TOutlookEventListener мы создадим только тогда, когда outlook действительно будет запущен.
class function TOutlookEventListener.IsOutlookRunning: boolean; begin result := true; try getActiveOleObject('Outlook.Application'); except result := false; end; end;Если объект outlook.application не найден (не запущен), то функция getActiveOleObject возбуждает исключение. Для обратной связи с главным приложением мы будем запоминать handle главной формы, и с его помощью уведомлять главное приложение о таких событиях, как получение письма. Т.е задача нашего класса - узнать о возникновении события, и уведомить о нем главное приложение (форму). В общем то, данный класс можно реализовать в DLL, и тогда уведомлять именно приложение (используя интерфейсы реализовывать библиотеки для работы с различными почтовыми клиентами, но вот только вряд ли какие то клиенты кроме аутлука способны информировать третьи программы о своих событиях). Таким образом, код события срабатывания таймера ожидания запуска почтового клиента в главной программе может быть таким:
procedure TMainForm.WaitForOutlookTimerTimer(Sender: TObject); begin if not TOutlookEventListener.IsOutlookRunning then exit; olEventListener := TOutlookEventListener.Create(); WaitForOutlookTimer.Enabled := false; olEventListener.NotifyHandle := self.Handle; end;Как мы видим, экземпляр объекта не создается, пока приложение outlook не запущено. А если оно запущено, то таймер останавливается. Мы вновь запустим его, когда приложение будет закрыто, и мы получим соответствующее уведомление. Если мы обнаружили, что Outlook (надоело переключать раскладку, буду писать аутлук(: ) запущен мы создаем непосредственно наш объект TOutlookEventListenter. Конструктор класса будет выглядеть следующим образом:
constructor TOutlookEventListener.Create(); var cpc : IConnectionPointContainer; ol : IDispatch; begin inherited; ol := GetActiveOleObject('Outlook.Application'); cpc := ol as IConnectionPointContainer; cpc.FindConnectionPoint(DIID_ApplicationEvents, connectionPoint); connectionPoint.Advise(self, cookie); end;Во-первых это безусловно родительский конструктор, во вторых необходимо получить запущенный объект приложения (мы уже знаем, что он запущен). Функция getActiveOleObject возвращает нам интерфейс IDispatch. Теперь нам следует использовать интерфейс IConnectionPointContainer для подключения к аутлук. Если мы изучим MSDN, а также импортированную библиотеку типов, то безусловно найдем описание интерфейса
ApplicationEvents = dispinterface ['{0006304E-0000-0000-C000-000000000046}'] procedure ItemSend(const Item: IDispatch; var Cancel: WordBool); dispid 61442; procedure NewMail; dispid 61443; procedure Reminder(const Item: IDispatch); dispid 61444; procedure OptionsPagesAdd(const Pages: PropertyPages); dispid 61445; procedure Startup; dispid 61446; procedure Quit; dispid 61447; end;который описывает набор методов для обработки соответствующих событий почтового клиента. Далее мы ищем как раз такую точку подключения, для интерфейса ApplicationEvents, используя метод FindConnectionPoint контейнера IConnectionPointContainer. "Точка подключения" сохраняется в private-члене класаа connectonPoint. Следующим шагом мы должны назначить объект, который будет реализовывать прием этих событий, и используя метод advise мы назначаем самих себя (поэтому наш объект и поддерживает интерфейс IDispatch). IDispatch определяет 4 основных метода, первые три мы не поддерживаем, возвращая значения указанные в документации по умолчанию:
function TOutlookEventListener.GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; begin result := E_NOTIMPL; end; function TOutlookEventListener.GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; begin pointer(TypeInfo) := nil; result := E_NOTIMPL; end; function TOutlookEventListener.GetTypeInfoCount(out Count: Integer): HResult; begin count := 1; result := E_NOTIMPL; end;Метод же Invoke обеспечивает нам запуск нужного действия при получении события:
function TOutlookEventListener.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; begin result := S_OK; case DispId of 61442 : ; // ItemSend(const Item: IDispatch; var Cancel: WordBool); 61443 : newMailEventAction(); 61444 : ; // Reminder(const Item: IDispatch); 61445 : ; // OptionsPagesAdd(const Pages: PropertyPages); 61446 : ; // Startup; 61447 : CloseEventAction(); else result := E_INVALIDARG; end; end;Как видно мы обрабатываем лишь два события: приход письма, и завершение работы почтового клиента. При получении письма, мы просто уведомляем главную форму/приложение о соответствующем событии:
const WM_OUTLOOK_EVENT_CLOSE = WM_USER + 1; WM_OUTLOOK_EVENT_NEWMAIL = WM_USER + 2; .... procedure TOutlookEventListener.newMailEventAction; begin PostMessage(notifyHandle, WM_OUTLOOK_EVENT_NEWMAIL, 0, 0); end;Если же аутлук закрывается, то мы отключаемся, и уведомляем форму:
procedure TOutlookEventListener.CloseEventAction(); begin try connectionPoint.Unadvise(cookie); connectionPoint := nil; PostMessage(notifyHandle, WM_OUTLOOK_EVENT_CLOSE, 0, 0); except {$IFDEF DEBUG} MessageDlg('Santa error! cant unadvise connection point', mtError, [mbOk],0); {$ENDIF} end; end;Таким образом, наш класс реализует некоторую прослойку между нашим приложением и аутлуком, просто передавая соответствующие уведомления/сообщения. Разобраться во всем помогла статья 2003 года про обработку событий outlook. Без нее я бы вряд ли узнал о существовании IConnectionPoint*. После всего этого, наша главная форма может получать два сообщения: новое письмо, и закрытие аутлука. Как главная формы будет обрабатывать данные сообщения это уже зависит от программы и не относится к данной статье.
Комментарии
Нет комментариев