Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bug in FormClosingEventArgs.CloseReason?

Tags:

c#

winforms

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.

like image 453
Slipfish Avatar asked Sep 02 '10 21:09

Slipfish


1 Answers

.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 WM_CLOSE (0x10) with closeReason = TaskManagerClosing

WM_CLOSE is sent by task manager and other applications to close a window and 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.None, null);
like image 99
Phil Wright Avatar answered Nov 12 '22 10:11

Phil Wright