views:

122

answers:

4

I'm having this issue with a live app.

(Unfortunately this is post-mortem debugging - I only have this stack trace. I've never seen this personally, nor am I able to reproduce).

I get this Exception:

message=Cannot access a disposed object.
Object name: 'Button'.
exceptionMessage=Cannot access a disposed object.
Object name: 'Button'.
exceptionDetails=System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'Button'.
   at System.Windows.Forms.Control.CreateHandle()
   at System.Windows.Forms.Control.get_Handle()
   at System.Windows.Forms.Control.PointToScreen(Point p)
   at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
   at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
   at System.Windows.Forms.Control.WndProc(Message& m)
   at System.Windows.Forms.ButtonBase.WndProc(Message& m)
   at System.Windows.Forms.Button.WndProc(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
exceptionSource=System.Windows.Forms
exceptionTargetSite=Void CreateHandle()

It looks like a mouse event is arriving at a form after the form has been disposed.

Note there is none of my code in this stack trace.

The only weird (?) thing I'm doing, is that I do tend to Dispose() Forms quite aggressively when I use them with ShowModal() (see "Aside" below).

EDIT: Just to clarify, I'm using C++-CLI so actually I don't call Dispose() I use the delete operator. This is the same as calling Dispose(), however.

But I only do this after ShowModal() has returned (that should be safe right?), and only when I'm done with the form.

I think I read that events might be queued up in the event queue, but I can't believe this would be the problem. I mean surely the framework must be tolerant to old messages? I can well imagine that under stress messages might back-log and surely the window might go away at any time?

Any ideas?

If you could even suggest ways of reproducing, that might be useful.

John


Aside:

TBH I've never quite understood whether calling Dispose() after Form.ShowDialog() is strictly necessary - the MSDN docs for ShowDialog() are to my mind a bit ambiguous.

+1  A: 

This happens if you show a form after disposing it. (I tried it)

After calling ShowDialog, you should dispose the form, but only if you don't plan to do anything else with that instance.

SLaks
No, that's not it. As I say, I only call Dispose() when I'm done with the form.
John
Are you sure? This is exactly what would happen if you do that. Do you call `Invoke` or `Text` on a control after disposing the form?
SLaks
I'm sure I'm not using it on the same thread. The Invoke is possible (see comment by casperOne in main). But if it was an Invoke then why would it appear as a Mouse event in the stack trace?
John
You're right; I'm not sure.
SLaks
Good thinking though - thanks :)
John
A: 

Why don't you use your form instanciation inside a using statement ? This would avoid the need to call dispose, and ensure you it's done at the correct time.

e.g. (not tested, don't have access to a compiler right now)

using(FormX frm = new FormX())
{
   DialogResult res = frm.ShowDialog();
   // Do your other stuff after
}
Shimrod
Actually I'm using C++/CLI so don't have "using" keyword. But, I'm definitely not using the form after I'm disposing. Still, good point though.
John
Oh ok, I was mislead by the C# tag ;-)
Shimrod
Oops! Sorry my bad!
John
+1  A: 

This is a very strange call stack. The button got disposed, its PointToScreen() method is recreating the handle. But it shouldn't have been able to get the mouse-up message if it was disposed. Only threading can really explain this.

Furthermore, nothing should have been disposed yet by the time the mouse-up message arrives. Presumably this is a button on the dialog that closes it. Make sure you use the Click event, not the MouseDown event. Also make sure you close the dialog by assigning its DialogResult property, not by calling Close(). Awkward in C++/CLI because it doesn't keep separate separate symbol tables for types and variables.

Ask the user what kind of "enhancements" she's got running on that machine.

Hans Passant
Agreed it's strange. When you say "Only threading can really explain this", can you elaborate a bit? Also why do you say one shouldn't call Close()?
John
At the bottom of the call stack the button had a Handle, at the top it didn't. Just trying to come up with other scenarios, a dialog should always be closed by setting its DialogResult property, not by calling Close.
Hans Passant
I'm still not sure why you shouldn't call Close(). The MSDN seems to suggest it is fine. Here is what it says on Form::Close(): "The two conditions when a form is not disposed on Close is when (1) ...; and (2) you have displayed the form using ShowDialog. In these cases, you will need to call Dispose manually to mark all of the form's controls for garbage collection.". To me that suggests it's not harmful to call Close(). Can you direct me to somehere that explains why Close() is bad in this case? Thanks!
John
PS: The reason why I prefer using Close() is that it means your code works if you use the form either as a Modal or non modal formPPS: Just in case it's not clear, I'm only using Close() from window events, I'm not using it from a different thread or anything crazy like that.
John
Strange. Calling Close() disposes the dialog, you cannot safely retrieve dialog results anymore. Are you ignoring the ShowDialog() return value? Not sure what you are doing.
Hans Passant
+1  A: 

I found your question while diagnosing a similarly odd stack trace in an application of mine. Here's the stack trace that I had to work with:

System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'TextBox'.
  at System.Windows.Forms.Control.CreateHandle()
  at System.Windows.Forms.TextBoxBase.CreateHandle()
  at System.Windows.Forms.Control.get_Handle()
  at System.Windows.Forms.Control.set_CaptureInternal(Boolean value)
  at System.Windows.Forms.Control.WmMouseDown(Message& m, MouseButtons button, Int32 clicks)
  at System.Windows.Forms.Control.WndProc(Message& m)
  at System.Windows.Forms.TextBoxBase.WndProc(Message& m)
  at System.Windows.Forms.TextBox.WndProc(Message& m)
  at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
  at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
  at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

It's not the same as yours, but it has some of the same key characteristics:

  • The handle exists at the beginning, but not at the end of the stack trace.
  • None of my code is visible in the stack trace.

I don't think my problem is the same is yours, but I thought I'd share what I discovered in the hopes that it can give you some insight. After some experimentation, I was able to reproduce my problem. I had hooked the LostFocus event on a different control and in certain situations the LostFocus event handler would delete certain controls that were no longer relevant.

However, if the LostFocus event was triggered because the user clicked on one of the controls to be deleted, I get the stack trace above. In my case, Control.WndProc calls Control.WmKillFocus which eventually calls my LostFocus event handler (of a different control), I dispose of the control that was clicked, and then Control.WmMouseDown gets called.

Might you have a similar situation occurring, where something is being triggered before WmMouseUp?

Using .NET Reflector to see which events might be called before WmMouseUp might help you track down the problem.

Daniel Stutzbach