Avoiding `ObjectDisposedException` while calling `Invoke` - c#

I have 2 forms, one is MainForm and second is DebugForm. The MainForm has a button that sets up and shows the DebugForm like this, And passes a reference to an already opened SerialPort:
private DebugForm DebugForm; //Field
private void menuToolsDebugger_Click(object sender, EventArgs e)
{
if (DebugForm != null)
{
DebugForm.BringToFront();
return;
}
DebugForm = new DebugForm(Connection);
DebugForm.Closed += delegate
{
WindowState = FormWindowState.Normal;
DebugForm = null;
};
DebugForm.Show();
}
In the DebugForm, I append a method to handle the DataReceived event of the serialport connection (in DebugForm's constructor):
public DebugForm(SerialPort connection)
{
InitializeComponent();
Connection = connection;
Connection.DataReceived += Connection_DataReceived;
}
Then in the Connection_DataReceived method, I update a TextBox in the DebugForm, that is using Invoke to do the update:
private void Connection_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
_buffer = Connection.ReadExisting();
Invoke(new EventHandler(AddReceivedPacketToTextBox));
}
But I have a problem. As soon as I close the DebugForm, it throws an ObjectDisposedException on the Invoke(new EventHandler(AddReceivedPacketToTextBox)); Line.
How can I fix this? Any tips/helps are welcome!
UPDATE
I found out if I remove the event in a button event click , and close the form in that button click, everything is fine and my debugform gets closed without any exception...how odd!
private void button1_Click(object sender, EventArgs e)
{
Connection.DataReceived -= Connection_DebugDataReceived;
this.Close();
}

Closing a form disposes of the Form object but cannot forcibly remove references that other classes have to it. When you register your form for events, you are basically giving a reference to your form object to the source of the events (the SerialPort instance in this case).
This means that, even though your form is closed, the event source (your SerialPort object) is still sending events to the form instance and the code to handle these events is still being run. The problem then is that when this code tries to update the disposed form (set its title, update its controls, call Invoke, &c.) you will get this exception.
So what you need to do is ensure that the event gets deregistered when your form closes. This is as simple as detecting that the form is closing and unregister the Connection_DataReceived event handler. Handily you can detect the form is closing by overriding the OnFormClosing method and unregistering the event in there:
protected override OnFormClosing(FormClosingEventArgs args)
{
Connection.DataReceived -= Connection_DataReceived;
}
I would also recommend moving the event registration to an override of the OnLoad method as otherwise it may receive events before the form has been fully constructed which could cause confusing exceptions.

You haven't shown the code for the AddReceivedPacketToTextBox method.
You could try checking for a disposed form in that method:
private void AddReceivedPacketToTextBox(object sender, EventArgs e)
{
if (this.IsDisposed) return;
...
}
Detaching the DataReceived event handler when closing the form is probably a good idea, but isn't sufficient: there is still a race condition which means your AddReceivedPacketToTextBox can be called after the form is closed/disposed. The sequence would be something like:
Worker thread: DataReceived event fired, Connection_DataReceived starts executing
UI thread: Form closed and disposed, DataReceived event detached.
Worker thread: calls Invoke
UI thread: AddReceivedPacketToTextBox executed while form is disposed.
I found out if I remove the event in a button event click , and close the form in that button click, everything is fine and my debugform gets closed without any exception...how odd!
That's not odd. Multithreading bugs ("Heisenbugs") are timing-related and small changes like that can affect the timing. But it's not a robust solution.

The problem could be solved by adding a timer:
bool formClosing = false;
private void Connection_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if (formClosing) return;
_buffer = Connection.ReadExisting();
Invoke(new EventHandler(AddReceivedPacketToTextBox));
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
base.OnFormClosing(e);
if (formClosing) return;
e.Cancel = true;
Timer tmr = new Timer();
tmr.Tick += Tmr_Tick;
tmr.Start();
formClosing = true;
}
void Tmr_Tick(object sender, EventArgs e)
{
((Timer)sender).Stop();
this.Close();
}
Thanks to JohnWein from MSDN

Related

Hide a Form when a new Form is shown and show it again when the previous Form is closed

How can I hide the Main Form when any of the Buttons that create and show another Form is clicked?
Closing the Form that has been opened from the Main Form, should cause the Main Form to show up again.
This is the code for the Click event of any of the buttons in the Main Form.
private void button7_Click(object sender, EventArgs e)
{
publisher p = new publisher();
p.Show();
}
You can add a handler to the FormClosed event of the For you're about to show, Hide() the current Form, then, when the FormClosed event is raised, remove the handler and Show() the current Form again.
The sender object is the object instance that raised the event, so you can cast sender to Form - (sender as Form) - and remove the handler you added before. You can use this event handler to handle the FormClosed event of other Forms that need the same treatment.
If you inspect the sender object, you'll see that the Form instance is your publisher instance, IsDisposed is still false and IsHandleCreated is still true, so you can still interact with it.
For example, you could read the value returned by public (standard or custom) properties, in case it's needed.
private void button7_Click(object sender, EventArgs e)
{
publisher p = new publisher();
p.FormClosed += OnFormClosed;
this.Hide();
p.Show();
}
private void OnFormClosed(object sender, EventArgs e)
{
(sender as Form).FormClosed -= OnFormClosed;
this.Show();
}
to hide a form:
private void button7_Click(object sender, EventArgs e)
{
publisher p = new publisher();
p.Show();
this.Hide();
}

Use Form.Show() but Not running code After Form.Show() Like ShowDialog()

I can't use Form.ShowDialog() I want use Form.Show() but I want execute code after From.Show() after Form.Closed
public void Tools(){
var frm=new ToolFrm();
frm.Show();
//do something
}
I want return true and hold execution while the form frm is open.
If you want to run some code when a modeless form is closed, then you can subscribe to to the form's FormClosed event:
public bool Tools()
{
var frm = new ToolFrm();
frm.FormClosed += Form_FormClosed;
frm.Show();
return true;
}
private static void Form_FormClosed(object sender, FormClosedEventArgs e)
{
((Form)sender).FormClosed -= Form_FormClosed;
MessageBox.Show("Form was closed");
}
Note that if you don't unsubscribe from the FormClosed event, then the subscribing object will keep alive a reference to the form until the subscribing object is garbage collected.
To prevent that behaviour, you can unsubscribe from the event in the handler, as I have done with the line:
((Form)sender).FormClosed -= Form_FormClosed;
If I understand your question correctly, you want to run code when the form is re-opened. The showdialog is actually a option you want and need to use.
private bool Tools()
{
this.Hide();
using (Form frm = new ToolFrm())
frm.ShowDialog();
DoSomethingYouWant(); //For example return true/false;
this.Show();
}
In the method DoSomethingYouWant() you can do anything you want to do.
The form will be hidden while the user untill the ToolFrm is closed.
If you want to run of example a new FormLoad() event, you can subscribe to it.
By replaceing the DoSomethingYouWant() with FormName_Load(this, e);
In the loading event you could do :
private void Form1_Load(object sender, EventArgs e)
{
if(Tools){
Messagebox.Show("true")
}
if(!Tools{
Messagebox.Show("false")
}
}

Stackoverflow Exception closing Form

I have a problem in WinForms. I created a MDIParent-Form and i call a ChildForm from the Load of the MDIParent. And I want that if the ChildForm closes, the MDIParent must close and the Application exits. Thats why i wrote an event for the childForm in the MDIParent, so that if the ChildForm closes the FormClosed-Event would be fired in the MDIParent, but it throws a stack overflow exception. I know that there is a infinite loop, but I dont know why...
private void MDIParent1_Load(object sender, EventArgs e)
{
Form1 childForm = new Form1();
childForm.MdiParent = this;
childForm.FormClosed += childForm_FormClosed;
childForm.Show();
}
void childForm_FormClosed(object sender, FormClosedEventArgs e)
{
this.Close();
//{Cannot evaluate expression because the current thread is in a stack overflow state.}
}
but if i use
Application.Exit();
instead of this.Close()... everything works fine ... i want to know why...can someone explain??
Update:
I have tried the same without a MDIParent and everything works...but why is there a problem if I use a MDIParent
This is a bit of a bug, the problem is that the child still is present in the MDIParent1.MdiChildren collection when the FormClosed event fires. In other words, the FormClosed event fires a little too soon. So when you close the parent, it will try to close the child again. Which triggers the child's FormClosed event again. Which closes the parent again. Etcetera. Event firing order is never not a problem. Well, let's call it a bug :)
The workaround is to use the Disposed event instead, it fires later:
private void MDIParent1_Load(object sender, EventArgs e)
{
Form1 childForm = new Form1();
childForm.MdiParent = this;
childForm.Disposed += childForm_Disposed;
childForm.Show();
}
void childForm_Disposed(object sender, EventArgs e)
{
this.Close(); // Fine now
}

c# trying to get an event to fire when a different window closes

I have one form that has an option to open another (dialogue). I want an event to fire when the second window closes. The first form is named frmMain() the other is frmAddEmployee(). Heres what I have:
in frmMain()
//create and open the second window
public void (object sender, EventArgs e)
{
frmAddEmployee addEmp = new frmAddEmployee();
addEmp.ShowDialogue();
}
//create event to handle addEmp being closed
public void addEmp_Closing(object sender, EventArgs e)
{
PopulateEmployeeList();
}
I'm not sure the event is being recognized as an event. What am I doing wrong?
Events in C# have to be registered manually - the C# compiler will not automatically register method as an event handler based just on the name of the method. You need:
frmAddEmployee addEmp = new frmAddEmployee();
addEmp.Closing += addEmp_Closing; // Register event handler explicitly
addEmp.ShowDialogue();
Automatic registration of events is done in ASP.NET and Visual Basic has Handles clause, but in C#, you need to use the += operator to specify that some method should be called when an event occurs.
Assuming ShowDialogue means ShowDialog, then it shows the form modally and you don't need an event handler:
//create and open the second window
public void (object sender, EventArgs e)
{
frmAddEmployee addEmp = new frmAddEmployee();
addEmp.ShowDialog();
PopulateEmployeeList();
}
If you don't show the second form modally, then you can hook up the event handler before showing the form like this:
public void (object sender, EventArgs e)
{
frmAddEmployee addEmp = new frmAddEmployee();
addEmp.FormClosed += AddEmpClosed;
addEmp.Show();
}
private void AddEmpClosed(object sender, FormClosedEventArgs e)
{
PopluateEmployeeList();
}
There is Closing and Closed events which you can register for on the Form. You are registered for neither, unless your registration is taking place somehwere else?
Before you call addEmp.ShowDialog() you need to set your method to handle the Closing event:
frmAddEmployee addEmp = new frmAddEmployee();
addEmp.Closing += addEmp_Closing;
addEmp.ShowDialogue();

C# Why does form.Close() not close the form?

I have a button click event handler with the following pseudo code:
private void btnSave_Click(object sender, EventArgs e)
{
if(txt.Text.length == 0)
this.Close();
else
// Do something else
// Some other code...
}
This is just some simple code, but the point is, when the text length equals zero, I want to close the form. But instead of closing the form the code executes the part // Some other code. After the click event handler is completely executed, then the form is closed.
I know, when I place return right after this.Close() the form will close, but I'd like to know WHY the form isn't direclty closed when you call this.Close(). Why is the rest of the event handler executed?
The rest of the event handler is executed because you did not leave the method. It is as simple as that.
Calling this.Close() does not immediately "delete" the form (and the current event handler). The form will be collected later on by the garbage collector if there are no more references to the form.
this.Close() is nothing than a regular method call, and unless the method throws an exception you will stay in the context of your current method.
Close only hides the form; the form is still alive and won't receive another Load event if you show it again.
To actually delete it from memory, use Dispose().
Answer is simple as you are executing your current method so this.Close() will be enqueued until either you explicitly returned or your current excuting method throws an exception.
Another possible solution is that if you open a new Form and want to close the current one: if you use newForm.ShowDialog() instead of newForm.Show() it doesn't close the currentForm with currentForm.Close() until the newForm is also closed.
Unless the Form is a modal form(opened with .ShowDialog()), Form.Close() disposes the form, as well. So, you cannot reopen it under any circumstances after that, despite of what others may have said. There is Form.Visible for this behavior(hiding/showing the form).
The point here is that .Close() does not return from the section it is called for several reasons. For example, you may call SomeForm.Close() from another form or a class or whatever.
Close() is just a method like any other. You have to explicitly return from a method that calls Close() if this is what you want.
Calling MessageBox.Show(frmMain,"a message","a title") adds the form "TextDialog" to the application's Application.OpenForms() forms collection, along-side the frmMain Main form itself. It remains after you close the Messagebox.
When this happens and you call the OK button delegate to close the main form, calling frmMain.Close() will not work, the main form will not disappear and the program will not terminate as it usually will after you exit the OK delegate. Only Application.Exit() will close all of the garbage messagebox "TextDialog"s.
private void btnCloseForm_Click(object sender, EventArgs e)
{
FirstFrm.ActiveForm.Close();
}
and if you want close first form and open secound form do this :
private void btnCloseForm_Click(object sender, EventArgs e)
{
FirstFrm.ActiveForm.Close();
}
private void FirstFrm_FormClosed(object sender, FormClosedEventArgs e)
{
SecounfFrm frm = new SecounfFrm ();
frm.ShowDialog();
}
or you can do somting like that :
private void btnCloseForm_Click(object sender, EventArgs e)
{
this.Hide();
}
private void FirstFrm_VisibleChanged(object sender, EventArgs e)
{
if(this.Visible == false)
{
this.Close();
}
}
private void FirstFrm_FormClosed(object sender, FormClosedEventArgs e)
{
SecounfFrm frm = new SecounfFrm ();
frm.ShowDialog();
}

Categories