tags:

views:

70

answers:

2

The requirements I'm up against

About 12 people are using this application, but we only want to allow 4 to close the application through traditional methods (Alt+F4, File > Exit, Close)

If any other method is used (TaskManager, WindowsShutdown) or one of the allowed users close the application, we need to perform some clean up (Closing out some connection channels)

The Code I've used to satisfy said requirements

private void formClosing(object sender, FormClosingEventArgs e)
{
    // If a user is allowed to close the application, an empty file (filename)
    // will be in the root directory of the application.
    if(e.CloseReason == CloseReason.UserClosing && !File.Exists("filename"))
    {
        e.Cancel = true;
        return;
    }

    // Cleanup
}

The Problem

If a user (not allowed to close) attempts to close the application through traditional methods, then attempts to close using Task Manager the CloseReason enum doesn't seem to reset itself, thus causing Task Manager to pop the prompt to force close, preventing the application from cleaning up.

The Question

Is this a bug, or am I missing something, something that will reset the CloseReason after the FormClosing event has been cancelled.

+2  A: 

.NET Reflector is your friend when working out how WinForms is operating.

The Form class has an internal field called closeReason and this is used when generating the event parameter that you examine in the Closing event. This internal field is set in four different places that I can find. These are...

1, The Form.Close() method sets the closeReason = UserClosing.

This makes sense as making a manual call to the Form.Close() method is usually the result of some user action, such as a File->Exit menu option being selected by the user. Clearly this is a user action.

2, The WM_SYSCOMMAND (SC_CLOSE) sets the closeReason = UserClosing.

The WndProc of the Form processes the *SC_CLOSE* system command by setting the closeReason to UserClosing and the lets the default window proc execute and close the application. This makes sense as this *SC_CLOSE* is sent when the user presses the window close chrome button or selected the close option from right clicking the title bar. Both are user actions and so setting the closeReason to UserClosing appears correct.

3, WndProc processes message 0x10 with closeReadon = TaskManagerClosing

I cannot find out what windows message 0x10 is but it seems to be related to the task manager because if the closeReason is currently equal to None it updates it to TaskManagerClosing. Note this issue with it being updated only if it is None as I think this is a problem for you.

4, WndProc processes messages 0x11 and 0x16 with closeReason = WindowsShutDown

This is not very interesting as you do not care about this scenario but it is just standard processing of shut down messages.

So the core problem you are having is that at no point is the closeReason being reset back to None when you cancel the Closing event. Therefore point number 3 above will never correctly update the value to TaskManagerClosing if that occurs after your cancel. As the closeReasson is an internal field you cannot update it directly. But you can cheat and this is an approach I have used myself in the past. You need to use reflection to get access to the internal field and then reset it to None when you set Cancel=true in your event handler.

I have not tested this code but you need something along the lines of...

PropertyInfo pi = typeof(Form).GetProperty("CloseReason",
                                           BindingFlags.Instance |
                                           BindingFlags.SetProperty |
                                           BindingFlags.NonPublic);

pi.SetValue(this, CloseReason.UserClosing, null);
Phil Wright
A: 

I think you cannot keep your process from shutting down if it's initiated by task manager (that is, OS... he's the 'big boss', it doesn't make sense that you can deny it something like closing your program).

The next best thing is to record the state of the application, and then instantiate another instance of your process with some startup options to take over the state you left. The OS would kill your process, but you will start another one immediately.

Also, if the user clicks in TaskManager "go to process" in the app list, and from there ends process, I don't think you'll be receiving any event at all...

Maybe it would be best if you had a windows service that's running behind the scenes and that keeps track that an instance is running. This way, users probably won't be aware that such process exists since it's not their application, and you can use that keep track of application shutdown.

veljkoz
It's not really that I'm try to prevent all ways to close the app, just the usual way for a few clients.
Slipfish