سؤال

I have a very strange behavior that only seems to happen on one form.

Basically I am creating an instance of a Form, and calling Show() to display the form non-blocking. In that form's Load event handler, I have some logic that may call this.Close() under certain circumstances. This closes the form, but then the form Show() method in the client code throws an ObjectDisposedException.

The stack trace from the ObjectDisposedException is as follows:

at System.Windows.Forms.Control.CreateHandle()
at System.Windows.Forms.Form.CreateHandle()
at System.Windows.Forms.Control.get_Handle()
at System.Windows.Forms.ContainerControl.FocusActiveControlInternal()
at System.Windows.Forms.Form.SetVisibleCore(Boolean value)
at System.Windows.Forms.Control.Show()
...etc.

This is what I'm seeing happen:

  1. Control.Show() is called
  2. my form is launched
  3. the OnFormLoad method is called
  4. the FormLoad event handler is called, inside of which I call this.Close()
  5. the OnFormClosing method is called
  6. the FormClosing event handler is called
  7. Dispose is called on my form and all it's user controls

and then somewhere toward the end of the Control.Show() method, it tries to get a handle to the form, which freaks out and throws an exception because the object is marked disposed.

My real question is, why can I do this exact same thing on every other form I have without exceptions? Is it a GC issue? I've tried putting a GC.Collect() call right after the this.Close() and it makes no difference. Like I said, it happens 100% of the time on this form, and never anywhere else, regardless of child user controls, scope of the form variable, etc.

Any ideas?

هل كانت مفيدة؟

المحلول

I know this is an old issue but no one seemed to have posted the obvoius answer.

You say you call Control.Show() and then Form.Close() and then the form is Disposed of. Well, unless you use MDI or use ShowDialog that's just as documented. Though, the short version of the Close() documentation is "Closes the form", it actually also disposes it implicitly under certain conditions.

See the remarks section: http://msdn.microsoft.com/en-us/library/system.windows.forms.form.close.aspx

If you want to show a form again. Use the Hide() method instead of Close().

Hope that helps other searching souls.

And guys, don't stop searching at "I don't know why it works sometimes". That becomes buggy software with lots of defensive "I'll call this method again just in case" stuff. Not good.

نصائح أخرى

The best way to do so :

 this.BeginInvoke(new MethodInvoker(this.Close));

this is the most simple way you wont get ObjectDisposedException

Ok, hate to answer my own question, but this was driving me nuts, and it was one of the hardest bugs to reproduce I've ever seen.

On my form I'm overriding the OnFormLoad and OnFormClose methods, where I save/restore the form's Size, Location, and WindowState to/from the registry. I took this code out and it fixed the problem. The weird thing is, I put it back and the problem didn't come back.

I finally reproduced the problem: you have to let the form open fully, maximize it, and then close it so that the Maximized state is saved to the registry. Then when you open it again, it will set it to Maximized, and if it closes in the Load handler, it tries to access the Size/Location as it's closing. Apparently accessing these values in the OnFormClosing method causes the form to try to focus IF AND ONLY IF the form is maximized, which is illegal, since the form has been disposed.

So basically, you can't access Form display properties in the OnFormClosing method of a form, if that form is going to call Close from it's Load event.(Unless you check the Disposed prop first)

pretty specific piece of Winforms wisdom I know, but I'm writing it down anyway.

If you want to close a form as if the user pressed the cross in the upper right corner (usually means cancel), just add the following code.

this.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.Close();

This also works in the form load function:

private void MyForm_Load (object sender, EventArgs e)
{
    // do some initializations

    if (!ContinueLoadingForm())
    {
         this.DialogResult = System.Windows.Forms.DialogResult.Cancel;
         this.Close();
         return;
    }
    // continue loading the form
}

If you don't want the form to be visible for a short while, set the Visible property false (for example in the designer or constructor), and set it back to true when you are certain the program can continue loading.

In load event is not realy good idea close the form. Do it after the Activated event.

protected override void CreateHandle()
   {
        base.CreateHandle();

        if (FormMustClose)  //FormMustClose is a variable in the loadevent.
        {
            Close();
        }
    }

One possibility:

They may have a timer on this form, that is being initialized and enabled in their FormLoad event. The timer would need to be disabled and stopped as well, before the form was closed, if the timer is trying to access the form when it's fired.

I've seen forms before that do this...

It seems to me, without looking closely at it, that the cleanest way to accomplish what you want might be to make a custom form class deriving from Form, and override OnFormLoad(...) and/or Show() to check for your condition and cancel out early.

That said, I don't know why it would work sometimes and not other times.

Have you tried stepping into the .net code to see what line of code is being called when the exception is occuring? If you have VS 2008 you can do so by going to Tools --> Options --> Debugging and select the Enable .NET Framework Source Stepping. Be warned, this may take a while to download all of the necessary files, but this way you can step into the form.Show() and see exactly what is going on.

Ok, it turns out it's a little simpler and more generic than I thought, but still weird and obscure.

If you're saving/loading the form Size/Location/WindowState when the form loads/closes like we do, you have to make sure that the OnLoad method calls base.OnLoad first so that the Form Load event handler fires, and THEN set the properties. Not doing so will only cause a problem if the form calls Close from inside the Load method. You'll get an ObjectDisposedException on the Show call after the form closing event is done.

My head hurts.

Form.Shown() Is the trick too.

As I understand it, setting the DialogResult of the form will close the form - may have to be other than DialogResult.None. (i.e. you don't need to then call the Form.Close() method).

The issue is also in part that if elsewhere in code, you are accessing a property of the form or control within it, that may prevent the form from closing.

It may also be best if as has been suggested, you have a property e.g.

private bool _loadedOk = false; 

in your form which you set in your initialisation code. In one of the later events after Form_Loaded, you then interrogate this and close the form if it's false.

Perhaps someone can suggest the best event to do this in??

If you want to close the form without flicker, the best way I found was override SetVisibleCore Method:

public partial class MyForm : Form
{

...
    protected override void SetVisibleCore(bool value)
    {
        if (value && !IsHandleCreated && !ContinueLoadingForm())
        {
            base.SetVisibleCore(false);
            this.Close();
            return;
        }

        base.SetVisibleCore(value);
    }
}

Then you can simply do:

...
var myForm = new MyForm();
myForm.Show();
...

The Form only will appear if ContinueLoadingForm() be true, this works with ShowDialog() and Application.Run() as well.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top