I'm missing an Excel.Application.Quit or an Excel.Application.BeforeQuit event. Does anybody know a workaround to mimic these events?
I access Excel from a C# WinForms application via COM Interop. Given an Excel.Application object, how can I:
Preferrably prevent Excel from quitting?
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...
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?
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.
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:
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.
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();
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.
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.
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.
Why not just use the Application.ApplicationExit event to know when it is closed?
Related
I am writing a c# application that opens a number of PI ProcessBook display files. PI Processbook is a program that has embedded Visual Basic for Applications support, including a VBE environment.
The problem that I have is that a number of the displays I want to open have macros in them that run automatically when the display is opened. I have set the macro protection to maximum (which should disable macros completely!), but unfortunately there is something in the TypeLibrary that I am using that means that when a file is opened programatically, it bypasses macro security and the macros run anyway. Go figure....
I am therefore trying to find a way to programatically kill these macros, even if they have started. I have tried using SendKeys.Wait("^{BREAK}") to force into break mode. This "works", but is a bit hit-and-miss: I find that if the macro doesn't pause at some point, SetForegroundWindow can't force the VB Editor to the foreground to accept the sendkeys input. I therefore tried using sendmessage. I used SPY++ to see that CTRL+BREAK appears to send a WM_CANCELMODE message to the window. Apparently, this is somehow handled differently from literally ALT+Tabbing or clicking on the window with a mouse and pressing the CTRL+Break keys, in that the message gets queued, but does not appear to process until the macro has finished executing.
Does anyone have any other suggestions of things to try?
I am therefore trying to find a way to programatically kill these
macros, even if they have started.
Hold down the Shift key while opening the Spreadsheet and the Macro's wont run.
Otherwise you could try halting the code while its running: Ctrl + Alt + PauseBreak
Edit:
I have set the macro protection to maximum (which should disable
macros completely!)
Try disabling them:
I'm developing a vbnet/c#.NET based application that opens files with different applications(excel, word, etc).
The application is launched using Dim app As Process = Process.Start(ProcessProperties)
Now, when I have to terminate the process I use app.Kill() but I need to check if the document has been modified before killing it.
How can I handle that? And if it's possible, how can I launch the application native prompt for save?
Thanks
For office applications use Office Interop Assemblies, not Process.Start to start and control them. Here is an example code for Excel (in VB.NET). You should add Microsoft.Office.Interop.Excel.dll to the project references in order for this to work.
oExcel = New Microsof.Office.Interop.Excel.Application
oBook = oExcel.Workbooks.Open(filepath)
'Do your stuff
oBook.Close 'This will trigger the application native prompt if the document was modified
oExcel.Quit()
For other programs it depends much on a program
You can achieve behaviour close to what you require by calling Process.CloseMainWindow rather than Process.Kill.
The behavior of CloseMainWindow is identical to that of a user closing an application's main window using the system menu. Therefore, the request to exit the process by closing the main window does not force the application to quit immediately.
Data edited by the process or resources allocated to the process can be lost if you call Kill. Kill causes an abnormal process termination, and should be used only when necessary. CloseMainWindow enables an orderly termination of the process and closes all windows, so it is preferable for applications with an interface.
In the case of Office applications with unsaved changes, CloseMainWindow would launch the Save dialog. You would need to handle scenarios where the users presses “Cancel”, since that may result in the WaitForExit call blocking indefinitely.
For example:
// Launch Word application.
Process wordProcess =
Process.Start(#"C:\Program Files (x86)\Microsoft Office\Office12\winword.exe");
// Give user some time to type in text.
Thread.Sleep(TimeSpan.FromSeconds(20));
// Request Word to close.
wordProcess.CloseMainWindow();
// Wait until user saves or discards changes.
// May block indefinitely if user cancels.
wordProcess.WaitForExit();
There are different approaches for this problem. You can calculate some sort of initial check-sum and see whether your document has any changes by redoing the check-sum and comparing against the original one.
This part is not very clear from your question, If the document gets saved, probably you can look at the date_modified value of the file to see whether there has been any modifications.
I've written a WPF/C#-based "shell" which launches WPF apps or other apps.
What would be the best method for checking if the process is finally fully launched or no longer "busy"? I've noticed that the mouse cursor for the launched process stays at the busy cursor from initial launch until I can finally see the UI for the process. Could I use User32.SetCapture API to set the mouse capture to the external process, then somehow check if the mouse cursor is the busy cursor? Or perhaps there's a mechanism in the System.Diagnostics.Process class that I'm unaware of?
As some of the launched apps are pre-compiled third-party apps, I absolutely cannot implement a mechanism in the external processes to message if it is finally ready, such as: Microsoft PowerPoint 2010 Viewer, Adobe Acrobat, or Adobe Flash Player Standalone.
I can't just check if the process has been created, because then I have a blank, unresponding window and a busy cursor. I hope to hide my WPF app the moment the external process is done launching.
The WaitForInputIdle Win32 APi function will wait until given process enters the message loop (with no input pending).
Quote: "Before trying to communicate with the child process, the parent process can use the WaitForInputIdle function to determine when the child's initialization has been completed."
You can call it via P/Invoke.
Not very cear what do you mean saying "beasy", but hear are several considerations:
There is no known (clear) way, at least that I'm aware of, that can let you do something like that. The thing is that process is perfectly isolated OS kernel citizen. So you can not write something that works for all type processes, especially if they are 3rd part binaries.
What you can try to do, is get the MainWindow of the process (if there is any), get its handle, and filter OS messages untill you get for example WM_ACTIVATED.
But even if this could work in certain situations, in others can measurably fail. For example, process loaded but the program is not active, cause for some reason License window of the application appeared.
Let's see what others suggest, in my opinion, there is no generic and no single solution to cover minority of possible cases.
Good luck
I'm starting a external application with System.Diagnostics.Process, this external process at one moment opens up a dialog where user has type something and then click OK. What i need is to wait with my application(the one where i started the external process) until the user has inserted something and clicked OK. After that OK i have to do some more task on that external process and then close it.
Yes, it's possible. There are a number of ways to get window information starting with a process handle and/or ID. See this question and responses for getting started. You will most likely end up using P/Invoke to the Win32 API to get this accomplished but there are dozens of good examples for getting this done.
Once you have the window handle you can use a timer polling scheme to test for the presence, or in your case, presence and then the disappearance of a window.
This is possible but there are some work behind it. First you need to run your code as unmanaged code as you will need to hook on Windows OS events with the Win32 API.
So an option would be to have a loop looking for the dialog to open, pause what ever your code are doing and continue when the dialog are gone.
If the application you are starting exists after the user interacts with the dialog, then you can just call Process.WaitFroExit() and your code will not continue until the process you started has quit.
There are quite a few helpful functions for interacting with processes in the System.Diagnostics.Process class (that I assume you are using to start this external application)
I'm using the Excel COM interop to insert images (specifically EPS) into a spreadsheet. The images are inserted fine, but Excel ignores all the visible/background settings and steals the focus the display a dialog box saying something like "importing image". The dialog box only stays a fraction of a section, but it makes the screen flicker, and worse, when I'm inserting many images at once, it can monopolize the system for several seconds (including stealing keystrokes from the foreground process).
I'm setting the background options as follows:
Excel.Application xlApp = new Excel.Application();
xlApp.Visible = false;
xlApp.ScreenUpdating = false;
xlApp.DisplayAlerts = false;
Excel.Worksheet worksheet;
//....
worksheet.Pictures(Type.Missing).Insert(filename,Type.Missing); //steals focus
How can I get Excel to stay in the background here like it belongs?
I suspect this is caused by a component that Microsoft licensed that is badly behaved.
The only way to deal with situations like this is by intercepting the appropriate low-level Windows message and blocking it using a Win32 hook
The easiest way to do this, and, believe me, it's not pretty, is to use the CBT hook. CBT stands for "Computer Based Training." It is an ancient and nearly obsolete technology intended to make it possible to create training apps which watch what you're doing and respond accordingly. The only thing it's good for any more is hooking and preventing window activation in code you don't have access to. You could intercept the HCBT_ACTIVATE code and prevent a window from activating.
This would probably need to be done in C or C++.
Contrary to common sense, setting xlApp.ScreenUpdating = true; prevents focus steal! (source)
I'm not sure I have a good answer to your question (I would have left a comment, but apparently I don't have enough reputation yet), but in case it's helpful, I often also disable events
xlApp.EnableEvents = False
Obviously you have to be sensitive to this setting when you're relying on Excel events in your code, but just in case there are some unwanted events firing while you're inserting, this might help.
Good luck!