Link to home
Start Free TrialLog in
Avatar of dr_gonzo
dr_gonzo

asked on

Word-close event with OLE and dp3.0

Hi!

I have an app written in Delphi 3.0 that controles an instance of word via OLE. Word does not exactly behave the way one could expect in some cases.
I posted this to the Office group also, but since nowone of you reads those group I add it here too. I'm aware that this is a question that does not really commit to any area. This is the closest though.

I want my word document to tell me when the user has clicked the x-button(or control menu Close[Ctrl+F4].
If I make a template and writes an event handler for the Close-event it works so neat with a normal document, but my OLE-document never sends me any close-event it just kills the window...
How am I supposed to make word let my app know when it is closing rather than when it has closed. I want to be able to pop up some own dialogs before the actual close of the document...

by the way. It's the samething with the dirty-flag. It's always considered to be "False" if it's an OLE-document...
why the hell is that?
What can I do about it? How do I write my own Dirty-flag for a document?
Avatar of RBertora
RBertora
Flag of United Kingdom of Great Britain and Northern Ireland image

What I think you want to do is enable word to control your application too..

So:

Word is and ole server
Your App is an ole server.

then in word : This document place this code:

Private Sub Document_Close()
    Set myobject = CreateObject("RobWord")
        myobject.Beep
    Set myobject = Nothing
End Sub

where "RobWord" is the name of your application/ole server.

As you can see I just created a beep function that can be called but you can put what ever code you want in the beep function...

Hope this is enough for you to do what you want, I don't have D3 so telling you how to make a ole server is out of the question but it is easy enough.

Rob ;-)

P.S.
as for all your dirty stuff, I don't even have a clue as to what your'e on about and I don't have the time to check it out :-(







Avatar of dr_gonzo
dr_gonzo

ASKER

Well no. I start and control a session of word. Start it with this code

procedure TJITWordClass.EmbeddFromFile(FilNamn : String);
var
      WordCreateInfo      : TCreateInfo;
begin
      WordCreateInfo.CreateType := ctFromFile;
      WordCreateInfo.ShowAsIcon := False;
      WordCreateInfo.ClassID := ProgIDToClassID('Word.Document');
      WordCreateInfo.FileName := FilNamn;
      FOLEContainer.CreateObjectFromInfo(WordCreateInfo);
      {
      The FOLEContainer points to a delphi-component of
      class TOLEContainer
      }
end;


I then link an template to the document. That template contain a
"Sub Document_Close()" with the code I want to be executed when my document is closed by the user. The problem I have is that there is no "Sub Document_Close()" ever executed by word when the user exit the document.

If I link the template to an ordinary document the event is run, but not when it's started as an OLE-doc.
I want Word to send my application a message (with SendMessage) so my app can ask if I want to save the word doc as a file, then word should on my request save the document to a given file name and then continue with it's closing.

regards,
Dr. Gonzo
Gettting the olecontainer to sendmessage to your app is probably never going to happen.

I have always used the direct ole approach instead of using the oleconatainer object:


uses comobj{D4},OleAuto{D3};

var  WordApp : Variant;
  try
    WordApp := GetActiveOleObject('Word.Application');
  except on exception do
    WordApp := CreateOleObject('Word.Application');
  end;

  WordApp.Visible := True;
  WordApp.Caption := 'MyWord...';
  WordApp.WindowState := 1; //maximise


using this connection to word I have done every thing I ever wanted to with word atuomation.. with this my previous comment will work : ie the onclose event of the document object will be triggered.

Rob ;-)
P.S.
Tell me if you want to use this approach, I can help you with it.





Take a look on Demos\ActiveX\Olectnrs\olemdi.dpr demo.
In short it has form and olecontainer on the form. And it uses form's OnCloseQuery event to ask user 'Save changes to object?'.
If user click 'Yes' then it uses OleContainer.SaveAsDocument method to save modified object.

Hmm very impressive, I'll get back to you shortly..
Rob ;-)
Ok I modified that demo, the following code works like a charm for the X button, but not so good for alt F4.
you have a play with it perhaps it is sufficient for your needs.

Rob;-)


  public
    { Public declarations }
    procedure AppMessage(var Msg: TMsg; var Handled: Boolean);

  end;

var
  MainForm: TMainForm;

implementation

uses MDIChild, about;

{$R *.DFM}

procedure TMainForm.AppMessage(var Msg: TMsg; var Handled: Boolean);
begin
  Handled := False;
// 273 click on X
// 257 alt F4
  if (Msg.Message = 273) or (Msg.Message = 257) then
  begin
    showmessage('Do your processing here');
  end;
(*
  Note that several messages 257 are sent so you should make
  sure only one instance of your processing gets run
  I am sure you can figure that out using a boolean variable
  and some strategically timed changing of that variable
*)
end;
I think that the handler of OnCloseQuery fires in all cases (when you press 'X' button or when you press Alt+F4 or when you select 'Close' from menu).
What is purpose of AppMessage procedure?
From where should I call it?

From the first form your application loads.

 -check it out.
Rob ;-)
I guess we speak about different things.
Why you don't want to use OnCloseQuery event?
From which point in the first form specifically should I call your procedure, i.e how should I modify demo to call your procedure?
P.S.
I use D5. Maybe in D3 there is such procedure in this demo.
I checked D3 demos.
In the D3 this demo is under
Delphi 3\Demos\OleCtnrs\olemdi.dpr
Ohh thanks.

I'm sorry guys(and maybe girls...),
but I think I have missed to tell
you something important. I have
the AllowInPlace-property set to
False, because I need Word to be
running "for it self". If you take
the demo mentioned above and change
the AllowInPlace property to False
you'll se word getting on top of the
demo app. What I want to know is,
how do you know when the users clicks
the X-button in word, not in the MDI-
child of my app. Cause when word is
closed but the OLE-container still
active my saving-stuff is to late to
run, cause it needs word...
Actually I don't want the word's X,
There can be lots of documents there,
I want to know when my documents X
are clicked.

I want to trap the X-click in Word.
What I tried whas words
"Sub Docuument_Close()". It's executed
when a document is closed and the
document has that procedure or any
template linked to it.
In that VBA-procedure I use
SendMessage myprogramhWnd, etc etc
so that my programs WndProc knows
when it happens, it then should
execute the SaveCommand in word and
then go back to word VBA-script for
exiting (Sub Document_Close) and
allow the exit of the document and
then everythings fine.

This way works with ordinary documents.
It's just that the bloody
"Sub Document_Close"-VBA-event-handler
don't execute when started as an
OLE document, only "ordinary" doc's
Anyway around this?

\Dr. Gonzo
Vladika: I got confused between you adn drgonzo, I thought gonzo used that demo to create his app.. sorry.

GONZO: Because you are not using inplace : I REPEAT an earlier comment as it seems you missed it or didn't try it:

TRY THIS:

uses comobj{D4},OleAuto{D3};

var  WordApp : Variant;
  try
    WordApp := GetActiveOleObject('Word.Application');
  except on exception do
    WordApp := CreateOleObject('Word.Application');
  end;

  WordApp.Visible := True;
  WordApp.Caption := 'MyWord...';
  WordApp.WindowState := 1; //maximise


using this connection to word I have done every thing I ever wanted to with word atuomation.. with this my previous comment will work : ie THE ONCLOSE EVEN OF THE DOCUMENT OBJECT WILL BE TRIGGERED!!


Rob ;-)
P.S.
Tell me if you want to use this approach, I can help you with it.

Yes Rob.
I use that approach already.
just that I never get the
"Private Sub Document_Close()"
or
the AutoClose-macro to trigger.
OnClose???
what's that...
yeah plz help me just with that bit...

I'll check you code now.
keep in touch...

\Dr. Gonzo
Gonzo:

 ignore the TMainForm.AppMessage bit of code as it was only relevant to that demo vladika was talking about.


NOTE:

Remember to place your
  Sub Document_Close() //wordbasic code
int the THISDOCUMENT file (not normal file)
and it should work!


Rob;-)
   
Hmm well it doesn't.
I wonder what I'm doing wrong.
I have tried both ThisDocument
and having it in a template that my doc use. I call it let's call it myTempl.dot

Forget the OLE for a sec. If I go to word as usual and start a new document and then choose Tools->Templates and add-ons
(not sure that's the correct name,
i don't have the english version of
word)
and and choose myTempl.dot as template for the document.
When i close the document the AutoClose-macro or Sub document_close runs...

But not when I started it via OLE...

I don't know what the hell I'm doing wrong.
I am using the following:
Microsoft word 97 SR-2
Delphi 4 professional

in wordVBA there are 3 windows,

top left window
bottom left window
right main window

in the top left there is a tree structure:

Normal
  Microsoft Word Objects
    ThisDocument
  Modules
    NewMacros

double click on ThisDocument (not just single click - double click)

now in the main window have this:

Private Sub Document_Close()
    MsgBox ("hello")
End Sub

only that nothing else.

in your delphi app do the following:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls,comobj;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  WordApp : Variant;
implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
begin

  try
    WordApp := GetActiveOleObject('Word.Application');
  except on exception do
    WordApp := CreateOleObject('Word.Application');
  end;

  WordApp.Visible := True;
  WordApp.Caption := 'MyWord...';
  WordApp.WindowState := 1; //maximise
  WordApp.Documents.Add;
end;

end.


That opens word or connects to it, clicking X on the document will display the message box.

If that does not work on your machine then make sure your word version is the same as mine, if that fails get delphi 4 professional. (because it most definately works)

Rob ;-)

I check you exact code, and I think Iäm on to something.
I don't use the exact way you do cause it's rather slow using a variant,
so I do the same stuff via the type library.
Rewriting my hole code seems pretty messy to do right now.
But it sure gives a hint about whats wrong...

I'll keep in touch
\Dr. Gonzo
If you are using this method:
  WordApp := CoApplication_.Create;

This also works on my machine.

Rob ;-)

ASKER CERTIFIED SOLUTION
Avatar of vladika
vladika

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Vladika.

This seems really intresting. I have problems making it work though. It doesn't compile cause Delphi 3.0 sais
"undeclared identifier: InterfaceConnect"

though I include the OleCtrl.pas
that beacuse the function is private.

Could you mail me the example source .zip-ed?
a.ohman@jit.se

This looks intresting. If the event ever go from Word at all that is.
Cause till now it's really look like Word doesn't send any events.
But I'd like to try this one out anyway.

\Dr. Gonzo
Ok. I sent it.
Just in case I wrote source for this functions.

procedure InterfaceConnect(const Source: IUnknown; const IID: TIID;
  const Sink: IUnknown; var Connection: Longint);
var
  CPC: IConnectionPointContainer;
  CP: IConnectionPoint;
begin
  Connection := 0;
  if Succeeded(Source.QueryInterface(IConnectionPointContainer, CPC)) then
    if Succeeded(CPC.FindConnectionPoint(IID, CP)) then
      CP.Advise(Sink, Connection);
end;

procedure InterfaceDisconnect(const Source: IUnknown; const IID: TIID;
  var Connection: Longint);
var
  CPC: IConnectionPointContainer;
  CP: IConnectionPoint;
begin
  if Connection <> 0 then
    if Succeeded(Source.QueryInterface(IConnectionPointContainer, CPC)) then
      if Succeeded(CPC.FindConnectionPoint(IID, CP)) then
        if Succeeded(CP.Unadvise(Connection)) then Connection := 0;
end;

Just watching now as you two seem to have things cooking nicely :-)

But I'd be very surprised if you can get D5 to work with D3.

Rob ;-)
Ok. I sent it.
Just in case I wrote source for this functions.

procedure InterfaceConnect(const Source: IUnknown; const IID: TIID;
  const Sink: IUnknown; var Connection: Longint);
var
  CPC: IConnectionPointContainer;
  CP: IConnectionPoint;
begin
  Connection := 0;
  if Succeeded(Source.QueryInterface(IConnectionPointContainer, CPC)) then
    if Succeeded(CPC.FindConnectionPoint(IID, CP)) then
      CP.Advise(Sink, Connection);
end;

procedure InterfaceDisconnect(const Source: IUnknown; const IID: TIID;
  var Connection: Longint);
var
  CPC: IConnectionPointContainer;
  CP: IConnectionPoint;
begin
  if Connection <> 0 then
    if Succeeded(Source.QueryInterface(IConnectionPointContainer, CPC)) then
      if Succeeded(CPC.FindConnectionPoint(IID, CP)) then
        if Succeeded(CP.Unadvise(Connection)) then Connection := 0;
end;

Sorry for reposting.
To: RBertora
Why not?
I do not use special D5 features, classes or functions. I have not D3 at hand just now. But when I will back to home (4 hours later) I will convert it to D3 certainly. :)


Hmm your code is very good Vladika, works well D4. (We W8C D3)

Remember though all of my stuff works well too.

For now I just wait and see what DR Gonzo says.

Rob ;-)
Works nice with D3 i think.
It's seems to be the correct solution.
BUt integrating it in to my source code will take me a couple of minutes :-)
I'll keep in touch as soon as I can.
Maybe tommorrow or the day after.

just one over course question if you would happen to know...

Rob: You set the caption on word like this: WordApp.Caption := 'MyWord...';

I need to do the same to my document.
Even do it's an unsaved file, to get things to work it would be nice to set the caption on my document to something else than word "Document 1".
Can I do this?

(this has got nothing to do with the point. I just ask if anyone of you know, cause it will make me evaluate the answer faster)

The both of you(Rob, Vladika) has helped with this problem, if it works, you'll get point for it the both of you.
The same question is asked on the office-part of EE, so you can have 600 each...

just need to get it to work first...

Thanks for all help so far
\Dr. Gonzo
changing doc captions:

procedure TForm1.Button1Click(Sender: TObject);
begin
  try
    WordApp := GetActiveOleObject('Word.Application');
  except on exception do
    WordApp := CreateOleObject('Word.Application');
  end;

  WordApp.Visible := True;
  WordApp.Caption := 'MyWord...';
  WordApp.WindowState := 1; //maximise
  WordApp.Documents.Add;

end;


procedure TForm1.Button2Click(Sender: TObject);
begin
  WordApp.ActiveDocument.ActiveWindow.Caption := 'hello';
end;


Rob;-)

By the way very very kind of you to give so many points I really would like mine to be in the delphi group :-)

I guess you both want the point in the delphi group.
I'll cancel the message in the office-group. Put some really stupid question here on the delphi group.
And give 600 points for it, and give them to you...
Then the both of you are proberly happy...

\Dr. Gonzo
Bye the way thanks for the caption-code.
I figured it out myself at the same time the mail arrived :-)

tnx again,
Dr. Gonzo
Gonzo I be happy with 100pts
Rob ;-)
I think so. 600 pts is very much.
But I think you should not cancel this question. It should stay for history :)
How can we reduce cost of question?
Also look at http://www.intac.com/~bly/com/resources/downloads.htm
"EventSinkImp is a utility that imports sink interfaces from COM server type libraries and automatically produces code for a Delphi non-visual component that publishes the sink methods as Delphi events."
1st. The information is well worth 600 points. Even 2*600 cause it's the diffrence of a working app and a nonworking app and I could never gotten this far without this help.

Vladika: is that link alright?
I can't go there...

and no I woun't cancel *this* question. I'll cancel the copy on the Office-group. There are no good answers there anyway. Not a single intelligent comment :-) users you know...

\Dr. Gonzo
Check link now. Some time ago I couldnot open link too. But now it works.
yeah, works like a charm...

\Dr. Gonzo
Okey. Now my program is up and running.
Thanks to you guys.
Vladikas answer with source code was the best so you'll have the point here.

RBertora:
I'm adding a question in the delpgi group named "points for RBertora"
comment something and you'll get your points to.

thanks once more,
Dr. Gonzo