views:

333

answers:

5

Update: I still don't have a viable solution to this question, new proposals are welcome!

I'm missing an Excel.Application.Quit or an Excel.Application.BeforeQuit event. Does anybody know a workaround to mimic these events?

Given an Excel.Application object (COM Interop in C#), how can I:

  1. Preferrably prevent Excel from quitting?
  2. If this is not possible, how can I at least notice when Excel is quit?

Please note: Since I have a COM reference to the Excel.Application, the Excel process does not exit when Excel is "quit" by the user. Although this sounds contradictory, that's how it is. By "quit" I mean that the user hits "Quit" or the "cross button" at the top right corner of the window. The window closes, the files are unloaded, the add-ins are unloaded and whatever stuff Excel does apart from that which I have no clue of. But I can still use the Application object to "revive" the process and make Excel visible again, though the add-ins are then missing, and I have no certainty about what else is in an undefined state.

To get rid of this problem, I would like to either Cancel the Quit at the very start (Think of a BeforeQuit Cancel = true if it existed), or at least be notified when Excel is quit, so I can release the COM objects and make the process really exit, and next time I need Excel again, I will know that I need to start it up first.

Unfortunately it's a vicious circle: As long as Excel runs, I need the COM objects. So I can't dispose of them before Excel is quit. On the other hand, as long as the COM objects are there, the process doesn't exit even if Excel pretends to quit, so I cannot wait for a process exit event or similar.

I have the unpleasing feeling that I'm going to bash my head against a brick wall...

+1  A: 

Why don't you just execute a System.Diagnostics.Process.Start(@"SomeWorkbook.xlsx"); to ensure that Excel is started. If it already has been started then this will not create a new process.

Enigmativity
because (1) I need to communicate with the workbook over COM, and (2) I need to open the workbook with a password programmatically, so the user doesn't have to enter it.
chiccodoro
@`chiccodoro` - You can (1) still communicate via COM - using `Process.Start` just ensures that Excel is running. And (2) you didn't mention anything about a password in your question, but you did say that you could start it via `Process.Start`. Perhaps you need to provide us with more detail?
Enigmativity
@Enigmativity: Sorry for confusing you, will update my question accordingly.
chiccodoro
Have completely rephrased my question to narrow down the scope. Your answer looks a little bit off the mark now, sorry for that.
chiccodoro
+5  A: 

There is a KB article, How to automate Excel and then know the user closed it, in C++. I haven't ported this to C#, but it's likely not a lot of work.

Otaku
If you properly cleanup your COMObjects with `Marshal.ReleaseComObject` like Otaku noted in his answer the process will exit onQuit. Releasing Office-Interop Objects is further described in this question: http://stackoverflow.com/questions/158706/how-to-properly-clean-up-excel-interop-objects-in-c I do not have a Excel-2007 version at hand so I am not sure weather there is an EventHandler for ApplicationQuit. The Process solution seems to be your best bet.
marg
I'm sorry for being harsh. Was just frustrated to try my best to formulate a precise question, slice of 150 rep for a bounty and then see an answer being top-voted which does not solve my problem and might prevent other users from investigating on this thread. - I realized that the misunderstanding comes from me not saying that I need the objects the very whole time Excel runs to react on events, and because if Excel isn't quit I want to keep the `Application` object to reuse it next time it is needed to improve performance. Still I might combine your proposal with a variant of Simon's maybe.
chiccodoro
@chiccodoro: no problem, based on reading through all the edits, I'm seeing clearer what you're after here. I've edited the above with the only known solution from MSFT on the subject.
Otaku
@Otaku: Will have a look to it, thanks. I have removed my first comment because now it doesn't make any sense anymore.
chiccodoro
@Otaku: If I got it right, the code essentialy creates an `Application` only to get the handle of the process, then disposes of the object again and then waits for the process to exit. So it's still not appropriate for my situation: I have an `Application` object because I need it (until the very end of Excel)...
chiccodoro
@chiccodoro: It doesn't release them unless you want them too. All the code is doing is cleaning up things in an event handler, `OnSeexlquit`. In that event handler, you can do whatever you like, like save your file, etc. All it is telling you is that Excel is going to be quit (the X is click on to quit), so close the right things and clean up. This answers your #2 above, not your #1.
Otaku
@Otaku: `OnSeexlquit` is the event handler of a button, not the event handler for when Excel is quit. The code in it starts Excel via COM, opens a workbook, then disposes of the objects again, and then waits for the process to exit. It *first* destroys the objects and *then* waits for the process to exit, still the same as you had written in your answer before you changed it. In the time between releasing the objects and the user hitting X, I don't have any control over the application anymore. On the other hand, if I don't release them, the process won't exit. Still the same problem.
chiccodoro
@chiccodoro: Again, this answers your #2 *If this is not possible, how can I at least notice when Excel is quit?*
Otaku
@Otaku: I feel sorry not to award your answer, I'd split the points and award a part of them to you if it was possible because you investigated quite some time. At least your answer was upvoted several times :-) However you're approach does *not even work for #2* because the process won't exit as long as I keep the `Application` object, but I need to keep the `Application` object until I'm notified of the "Quit". On the other hand as long as the process doesn't exit, the given code won't notify me of the "Quit" user action.
chiccodoro
+3  A: 

It's a hack of course, but couldn't you use the Windows SetWindowsHookEx API with WH_SHELL or WH_CBT at least to get notified of the Excel's main window being destroyed?

NOTE: It certainly has security implications, i.e. need some admin rights to do cross-process magic.

Simon Mourier
Hi Simon. You have some point there. Instead I could also choose to observe the `Excel.Application.Visible` property. I didn't want to use that so far, because suppose that a VBA macro sets `Visible = false` for any reason, it would recognize it as a Quit. Although, in combination with Otaku's proposal it might be promising.
chiccodoro
This is what white box testing apps do. They use Windows hook api calls to get the parent window and control it via mouse/keyboard automation. You will however find problems I think with this approach... for instance unloading of resources after the process is terminated, in that case you may still have issues with addins being disabled.
Anonymous Type
+3  A: 

The problem you are trying to solve here is not going to be solved by monitoring for program exit. Before you say that I am not answering your question, you state in the question that you are able to revive excel even after the user quits excel. Therefore the excel.exe process is still in play because you have a .net object with a com interop reference to excel.application.

So you have three options:

  1. Avoid the user exiting Excel. As you have stated keep Excel from quitting, however I am unaware of a way that you can prevent the user from causing Excel to quit, thus as you have correctly noted unloading your and any other addins, etc. Bare in mind that Microsoft specifically design the user interaction this way, they want users to have the ability to close their apps. your addin needs to be able to deal with this, if it can't I'd say thats a problem with your addin not Excel. I might be wrong since i dont know enough about your apps requirements.

  2. Cleanup all unmanaged resources BEFORE the user quits. What you need to do is cleanup your references to alll Excel and Office unmanaged resources before the user manually quits Excel so that when they do quit your application code is not left with any leftover resources that are now pointing to an instance of excel that no longer has addins etc loaded. Step (a) should be performed as you go, as soon as you no longer need a particular resource or even when reusing it for something else (i.e. a Excel.Range type) whereas step (b) should be used less often however if its a win app and not a addin, probably alot more frequently, it all depends on your app and the window of oppurtunity that you have (time) before the user is likely to complete there tasks an shutdown. Obviously with an addin you can just put it in the shutdown event, or arbitarily in your code.

    a. As noted by Otaku, use Marshal.FinalReleaseCOMObject on each unmanaged resource that is != null after use.

        if (ComObject != null)
        {
            Marshal.FinalReleaseComObject(ComObject);
            ComObject = null;
        }
    

    b. use the GC cleanup pattern for COM resources.

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        GC.WaitForPendingFinalizers();
    
  3. Reload addins If your not interested in fully tracking down and unloading all unmanaged resources due to the complexity of that task, time constraints (although I'd recommend it), you could look at reloading any required addins that you are presumably already aware of in your environment. This only works if you control the environment. there are techniques for loading both Excel and COM addins manually. As for other stuff, I'm not aware of that but perhaps its possible if you are using XLLs or maybe XLT in startup/XLSTART dirs but that would load anyways.

Anonymous Type
There is perhaps another "hack" option but that requires you to force the user to open their workbooks in a seperate instance of Excel.exe and then kill off your references to that instance.
Anonymous Type
Hi Anonymous Type, thanks for your elaborate reply! Your "introduction" says very well what I've been trying to explain. Concerning 1, I haven't developed the add-ins but they are needed for working with the files that my application manages. All add-ins are unloaded by Excel on Quit, i.e. if I revive that Excel, they aren't available anymore. As for 3, I tried to reload add-ins, but it didn't work and turned out error-prone. Concerning 2: The problem is that I need the objects *as long as Excel is running*, because I have to observe some events, e.g. BeforeSave event, and say Cancel = true...
chiccodoro
ok i understand you need to observe the state of certain events such as BeforeSave, etc, however if as I assume you have an (external to Excel.exe) application running that is monitoring for those events on each instance of Excel, can you not just serialise and write the current states of these events to a config/xml file if that instance of Excel.exe is closed? You say you supposedly have a chicken n' egg scenario concerning events in Excel, but this is not really a workable program scenario. At some point you need to be able to handle the fact that your user is offline from Excel.exe
Anonymous Type
I guess I don't really understand enough about your program scenario based on the requirements you have defined in the question. I think you should elaborate if possible on some more specifics about what you are doing with these events (e.g. BeforeSave)..
Anonymous Type
+4  A: 

Please note that I haven't tried this.

Create a workbook which has code in it on BeforeClose.
for e.g.

Option Explicit

Private Sub Workbook_BeforeClose(Cancel As Boolean)
    Cancel = True
End Sub

Open this workbook alongwith other workbooks that you have & it doesn't have to be hidden (if the entire application is invisible).

So, if you try to quit the excel instance, it will force closing of this hidden workbook, which will raise its BeforeClose event & you can write code to stop it from closing.

Note that above code is in VB6 (VBA) and it will need converting into c#.
Post a comment, if you find any difficulty converting.

If you want to hide a workbook, you could do

Workbooks("my workbook").Windows(1).Visible = False 

Note: Workbook has a Windows collection. The code above tries to hide the 1st window.
I don't know, can a workbook have more than 1 window? if so, how?

shahkalpesh
This approach sounds very promising and could also be used to be noticed of Quit, for I can notice a workbook-close event by observing the workbooks collection (http://stackoverflow.com/questions/2767439/excel-automation-close-event-missing)
chiccodoro
Hi shahkalpesh. I've tried it and it works fine! I feel somehow uncomfortable to award such a dirty approach, but it's in fact the only answer that really achieves what discussed.
chiccodoro
@chiccodoro: Yes, it is dirty. If you happen to come across better approach, feel free to post it. That will surely help others from trying the hack :). Thanks for accepting the answer too.
shahkalpesh
hope this works flawlessly, but althougth i don't have a url handy to give you as reference I remember reading of situations that can occur where this particular "method" is far from full proof in preventing workbooks closing. anyway like I said hope it solves your problem.
Anonymous Type