2

I need to generate and send message with Outlook from application. Mail form should be displayed as modal, mostly because i generate attachment and it should be deleted when user send email (or discard it).

My problem is when i make Outlook dialog modal ("MailIt.Display(True)"), Outlook message window shown in background. Command "Outlook.ActiveWindow.Activate" brings it to front, but it can be called when windows already visible and so i can't call it if window is modal. I tried this code:

MailIt.Display(False);
OleVariant(Outlook.ActiveWindow).Activate;
MailIt.Display(True);

But id doesn't work, if form already displayed it normal mode it can not be switched to modal. Any ideas? My environment: Windows 8 (UAC disabled), XE3, Outlook 2010.

Just tried to send my form into background as suggested by Arioch:

SetWindowPos(AWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE);
MailIt.Display(AModal);
SetForegroundWindow(AWnd);

In this case Outlook became foreground (as i need it), but my from can became invisible (if there are other running apps with opened forms), so it also doesn't solve the problem. It should be Outlook in modal state on the top and my application is next to Outlook.

SetWindowPos(AWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE) 

Better then HWND_BOTTOM, but it is not guaranteed that Outlook became foreground.

EDITED2. Final (hopefully) solution based on events (suggestion from Kobik):

uses
  Vcl.OleServer, Winapi.ActiveX;

Type
  TOutlookMsgForm = class
  private
  protected
    FOutlook: OutlookApplication;
    FMessageSent: Boolean;

    procedure OnOpen(ASender: TObject; var Cancel: WordBool);
    procedure OnSend(ASender: TObject; var Cancel: WordBool);
    function  TryDisplayOutlookMail(const AMailTo, ASubject, ABody: string;
      const AAttachmentFileNames: array of string; AWnd: HWND; AModal: Boolean): boolean;

    property Outlook: OutlookApplication read FOutlook write FOutlook;
    property MessageSent: Boolean read FMessageSent write FMessageSent;
  public
    class function DisplayOutlookMail(const AMailTo, ASubject, ABody: string;
      const AAttachmentFileNames: array of string; AWnd: HWND; AModal: Boolean = True): boolean; static;
  end;

{ TOutlookMsgForm }

procedure TOutlookMsgForm.OnOpen(ASender: TObject; var Cancel: WordBool);
begin
  if (Outlook<>nil) and (Outlook.ActiveWindow<>nil) then
    OleVariant(Outlook.ActiveWindow).Activate;
end;

procedure TOutlookMsgForm.OnSend(ASender: TObject; var Cancel: WordBool);
begin
  Cancel := False;
  MessageSent := True;
end;

function TOutlookMsgForm.TryDisplayOutlookMail(const AMailTo, ASubject, ABody: string;
  const AAttachmentFileNames: array of string; AWnd: HWND; AModal: Boolean): boolean;
var
  MailIt: MailItem;
  Mail: TMailItem;
  i: Integer;
begin
  MessageSent := False;
  try
    OleInitialize(nil);
    try
      Outlook := CoOutlookApplication.Create;
      Mail := nil;
      try
        MailIt := Outlook.CreateItem(olMailItem) as MailItem;
        MailIt.To_ := AMailTo;
        MailIt.Subject := ASubject;
        MailIt.Body := ABody;
        for i := Low(AAttachmentFileNames) to High(AAttachmentFileNames) do
          MailIt.Attachments.Add(AAttachmentFileNames[i], EmptyParam, EmptyParam, EmptyParam);
        Mail := TMailItem.Create(nil);
        Mail.ConnectTo(MailIt);
        Mail.OnOpen := OnOpen;
        Mail.OnSend := OnSend;
        MailIt.Display(AModal);
        if AModal and (AWnd<>0) then
          SetForegroundWindow(AWnd);
        Result := true;
      finally
        FreeandNil(Mail);
        MailIt := nil;
        Outlook := nil;
      end;
    finally
      OleUnInitialize;
    end;
  except
    Result := False;
  end;
end;

So it is solved (try#3). Thanks!

Andrei Galatyn
  • 3,322
  • 2
  • 24
  • 38
  • can it be called from a separate thread ? – Arioch 'The Dec 17 '13 at 12:08
  • @Arioch: Yes, it is not a problem to call it in separate thread or call it in main thread but move to foreground from another thread. But i am not sure that it is safe to call Outlook.ActiveWindow.Activate from another thread. – Andrei Galatyn Dec 17 '13 at 12:13
  • @Arioch: From MSDN: "When a background thread makes a call to the Office application, the call is automatically marshaled across the STA boundary. ... Excel might be in a state such that it cannot immediately handle an incoming call. For example, the Office application might be displaying a modal dialog.". So seems if form is shown modal, then calls to object may not work from another thread. – Andrei Galatyn Dec 17 '13 at 12:25
  • 1
    Then maybe you can PostMessage YOUR window to go behind ? – Arioch 'The Dec 17 '13 at 12:35
  • @Arioch: Probably yes, but not sure how deep i have to bury my app :) I can't see any guarantee that Outlook will be next Z-order app. But at least it is some idea, i will try. thanks! – Andrei Galatyn Dec 17 '13 at 13:10
  • 3
    Similar Q: http://stackoverflow.com/questions/9972866/how-to-show-on-front-and-not-on-background-a-new-email-form-in-outlook-with-ol I can't reproduce with D5/XP :-P Have you tried to disable your window first? (see how Application modal loops work) – kobik Dec 17 '13 at 14:44
  • @Arioch: If i send my form to background, then outlook is on foreground, but my form may became invisible because of other apps. I can try to start separate thread to find outlook window and make it foreground, but it seems to be too dirty. – Andrei Galatyn Dec 17 '13 at 14:48
  • @kobik: In that case it was enough to call Activate (just posted it as answer there:), because it was not required to open it in modal state. In modal state it doesn't work, because modal Outlook doesn't became foreground (at least in W8 with Outlook2013). – Andrei Galatyn Dec 17 '13 at 14:59
  • 3
    You can create a `TMailItem` and connect to `MailIt` via `TMailItem.Connect(MailIt)`, then use `TMailItem.OnOpen := YourMailOnOpenEvent` and then on that event find Outlook window handle. just an thought... (Look here: http://stackoverflow.com/questions/5507342/outlook-object-model-detecting-if-email-has-been-sent) – kobik Dec 17 '13 at 15:18
  • 1
    @kobik: I just tested, it is enough to call SetWindowPos(AWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE) before Display(True) and then it is displayed as it should be. But thank you for idea with events! – Andrei Galatyn Dec 17 '13 at 15:25
  • @Arioch: Solution is based on your idea, so if you post it as answer, then i will accept it. – Andrei Galatyn Dec 17 '13 at 15:31
  • LOL, Funny I never even read my own comment in the similar Q I commented (So I **was** able to repro after all): `"OP is correct. if the Outlook main window is visible the new message is opened under the Delphi form. (try to run the EXE from outside the IDE)"` - Sorry to say, Your solution does not work for me, if Outlook is already opened and I switch windows from it to my application. the new `MailIt` message window remains in the back (on top of Main outlook application) – kobik Dec 17 '13 at 16:06
  • @kobik: Yes, unfortunately you are right, depending on current Z-order of forms it can be reproduced. Will try tomorrow to use events and bring outlook foreground on opening. – Andrei Galatyn Dec 17 '13 at 16:23
  • @kobik: Now it seems to be ok. So if you post you suggestion as answer i will have to accept it (@Arioch: sorry, man:). – Andrei Galatyn Dec 18 '13 at 08:08
  • @AndreiGalatyn, I think you have done an excellent job yourself. I just gave you the hint. post your own answer, and accept it. I'll up-vote (although I haven't tried your code yet) – kobik Dec 18 '13 at 20:09

1 Answers1

1

If you cannot bring Outlook to the front, maybe you can push your own applicatio nto the back by posting message from an auxiliary thread ?

Arioch 'The
  • 15,799
  • 35
  • 62