Solved

Cleaning up Event Handlers and Delegates?

Posted on 2006-07-02
17
603 Views
Last Modified: 2008-02-01
I am getting some strange and un-suspected behavior in my appllication at the moment. I am putting this down to the fact that I am not cleaning up my Event Handlers or Delegates correctly.

I was looking for the best possible way to do this, as Its not advised to do it like C++. Any suggestions or clear and concise links on how to clean up content that I know is not being garbage collected.

0
Comment
Question by:directxBOB
  • 10
  • 6
17 Comments
 
LVL 11

Expert Comment

by:anyoneis
ID: 17028905
Is this a windows forms app? If so, are you calling subforms with ShowDialog? If so, you may be thinking that the forms are getting destroyed when they close when in fact they are not? Or not. Your question just sounded like my question of a few months ago.

Can you be more explicit on what you are looking for?

David
0
 

Author Comment

by:directxBOB
ID: 17028930
Well thats pretty much the problem Im trying to track down.

Basically I have a bunch of forms when I close one it closes no problem. But when I re-open the same form again I start to get strange behaviour which I am putting down to the fact that I am not cleaning everything up correctly hence the strange behaviour. It works perfectly the first time the form is open, re-open it and its buggered.

Can I get a link to your question from before?
0
 
LVL 11

Expert Comment

by:anyoneis
ID: 17028965
The deal is, the forms are not destroyed when they close if ShowDialog is used. They are merely hidden. I was only ignorant of this rather important fact since the first .NET release. <sigh>

David
0
 
LVL 11

Expert Comment

by:anyoneis
ID: 17028969
So, you have two instances of the form the second time you display it. And both added their event handlers. So, weird things happen. I'll look for the Q.

David
0
 
LVL 9

Assisted Solution

by:WinterMuteUK
WinterMuteUK earned 450 total points
ID: 17029532

It does kind of depend on 'how' you're using your forms, whether you declare them locally to use them:

  private void ShowForm()
  {
     Form2 f2 = new Form2();
     f2.ShowDialog();
  }

The f2 instance will be disposed of at the end of the method. But if you're declaring the Form as a global member field:

   Form _F2;
   private void ShowForm()
   {
      _F2 = new Form2();
      _F2.ShowDialog();
      _F2.Dispose();
   }

then you will need to dispose of it explictly or use the 'using' keyword (for example):

  private Form2 _F2;
  private void ShowForm()
  {
     using(_F2 = new Form2())
     {
         f2.ShowDialog();
     }
  }

Hope that helps.

Cheers,
Wint
0
 
LVL 11

Expert Comment

by:anyoneis
ID: 17031130
That's the way it should work, but I ran into cases where it did not see to clean up properly in the local variable scenario. I ended up removing my own event handlers. I'll see if I can find an example.

David

0
 
LVL 9

Accepted Solution

by:
WinterMuteUK earned 450 total points
ID: 17031238
To remove the eventhandler just replace the += with a -=: i.e.

  f2.MyEvent += new EventHandler(MyHandler);
  f2.MyEvent -= new EventHandler(MyHandler);

There's nothing to stop you calling 'Dispose' in the local scenario:

   Form2 f2 = new Form2();
   f2.ShowDialog();
   f2.Dispose();

Wint.
0
 
LVL 11

Assisted Solution

by:anyoneis
anyoneis earned 50 total points
ID: 17031759
Yes, "RemoveHandler" (VB) or "-=" (C#) will remove the handlers. However, in VB the problem is a little stickier if you use the "Handles" keyword (I'm not sure you can "RemoveHander" if you didn't "Addhandler"). And it is really bad form to have to "correct for" autogenerated code (which is often the case with handler subscriptions.

Here is an example of how this problem got me in trouble, in case it is similar to yours, directxBOB:

// .. in a "ShowStuff" button handler:
if (something)
{
     StuffForm stuffForm = new StuffForm();
     stuffForm.ParticipantID = m_participantID;
     DialogResult dialogResult = stuffForm.ShowDialog();
} // <--- THIS IS THE END OF stuffForm, RIGHT????

// ... WRONG!!!

// Select the next BiggerStuff, which clears the stuff table that the StuffForm was looking at, and, lo and behold,
// StuffForm handlers are fired, specifically when I clear the table that StuffForm was looking at, the StuffForm's currency manager for the stuff table fires the CurrentOrPositionChanged event.

'Splaine that! :-)

It gets worse. Adding stuffForm.Dispose() does not solve the problem!

David
0
Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

 
LVL 11

Expert Comment

by:anyoneis
ID: 17034979

Interesting. I wrote a simple little demo program with a main form with two buttons - 1 to load a modal subform and one to clear a datatable. I create a datatable in the main form, add 4 records, and pass the ds (which contains the datatable) to the subform. It binds a datagrid to the datatable and hooks the currency manager's Positionchanged and CurrentChanged events.

I then exit the subform and dispose of it. Finally, I press the second button which clears the datatable in question. With VS2003, I get a single CurrentChanged event (from the form that "no longer exists"). Converting the project to VS2005, I get 3 CurrentChanged events!?!

Sure smells like a bug. DirectXBob, let me know if this is not the kind of thing you are seeing or if this is not helpful and I'll start my own Q. The code is below.

David

[Code]
//The main form
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace TestModal
{
      /// <summary>
      /// Summary description for Form1.
      /// </summary>
      public class Form1 : System.Windows.Forms.Form
      {
            /// <summary>
            /// Required designer variable.
            /// </summary>
            private System.ComponentModel.Container components = null;

            DataTable m_dt;
            private System.Windows.Forms.Button button1;
            private System.Windows.Forms.Button button2;
            DataSet m_ds;

            public Form1()
            {
                  //
                  // Required for Windows Form Designer support
                  //
                  InitializeComponent();

                  m_dt = new DataTable();            
                  m_dt.Columns.Add("ID", typeof(int));
                  m_dt.Columns.Add("Description", typeof(string));

                  m_ds = new DataSet();
                  m_ds.Tables.Add(m_dt);
            }

            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            protected override void Dispose( bool disposing )
            {
                  if( disposing )
                  {
                        if (components != null)
                        {
                              components.Dispose();
                        }
                  }
                  base.Dispose( disposing );
            }

            #region Windows Form Designer generated code
            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InitializeComponent()
            {
                  this.button1 = new System.Windows.Forms.Button();
                  this.button2 = new System.Windows.Forms.Button();
                  this.SuspendLayout();
                  //
                  // button1
                  //
                  this.button1.Location = new System.Drawing.Point(104, 56);
                  this.button1.Name = "button1";
                  this.button1.TabIndex = 0;
                  this.button1.Text = "Load Modal Form";
                  this.button1.Click += new System.EventHandler(this.button1_Click);
                  //
                  // button2
                  //
                  this.button2.Location = new System.Drawing.Point(104, 128);
                  this.button2.Name = "button2";
                  this.button2.TabIndex = 1;
                  this.button2.Text = "Clear Data";
                  this.button2.Click += new System.EventHandler(this.button2_Click);
                  //
                  // Form1
                  //
                  this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
                  this.ClientSize = new System.Drawing.Size(292, 273);
                  this.Controls.Add(this.button2);
                  this.Controls.Add(this.button1);
                  this.Name = "Form1";
                  this.Text = "Form1";
                  this.Load += new System.EventHandler(this.Form1_Load);
                  this.ResumeLayout(false);

            }
            #endregion

            /// <summary>
            /// The main entry point for the application.
            /// </summary>
            [STAThread]
            static void Main()
            {
                  Application.Run(new Form1());
            }

            private void Form1_Load(object sender, System.EventArgs e)
            {
            }

            private void button1_Click(object sender, System.EventArgs e)
            {
            m_dt.Rows.Add(new object[] { 1, "Part 1" });
            m_dt.Rows.Add(new object[] { 2, "Part 2" });
            m_dt.Rows.Add(new object[] { 3, "Part 3" });
            m_dt.Rows.Add(new object[] { 4, "Part 4" });

            DialogResult dialogResult = DialogResult.OK;
                  ModalForm1 mf1;
                  do
                  {
                        mf1 = new ModalForm1(m_ds);
                        dialogResult = mf1.ShowDialog();                        
                  } while (dialogResult == DialogResult.Retry);
            }

            private void button2_Click(object sender, System.EventArgs e)
            {
                  m_dt.Clear();
            }
      }
}


// The subform
using System;
using System.Data;      
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;

namespace TestModal
{
      /// <summary>
      /// Summary description for ModalForm1.
      /// </summary>
      public class ModalForm1 : System.Windows.Forms.Form
    {
            private System.Windows.Forms.DataGrid dataGrid1;
        private Button button1;
            /// <summary>
            /// Required designer variable.
            /// </summary>
            private System.ComponentModel.Container components = null;

            public ModalForm1(DataSet ds)
            {
                  //
                  // Required for Windows Form Designer support
                  //
                  InitializeComponent();

                  dataGrid1.DataSource = ds;
                  dataGrid1.DataMember = ds.Tables[0].TableName;

                  CurrencyManager cm = (CurrencyManager)BindingContext[dataGrid1.DataSource, dataGrid1.DataMember];
                  cm.PositionChanged +=new EventHandler(cm_PositionChanged);
                  cm.CurrentChanged +=new EventHandler(cm_CurrentChanged);
            }

            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            protected override void Dispose( bool disposing )
            {
                  if( disposing )
                  {
                        if(components != null)
                        {
                              components.Dispose();
                        }
                  }
                  base.Dispose( disposing );
            }

            #region Windows Form Designer generated code
            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InitializeComponent()
            {
            this.dataGrid1 = new System.Windows.Forms.DataGrid();
            this.button1 = new System.Windows.Forms.Button();
            ((System.ComponentModel.ISupportInitialize)(this.dataGrid1)).BeginInit();
            this.SuspendLayout();
            //
            // dataGrid1
            //
            this.dataGrid1.DataMember = "";
            this.dataGrid1.Dock = System.Windows.Forms.DockStyle.Bottom;
            this.dataGrid1.HeaderForeColor = System.Drawing.SystemColors.ControlText;
            this.dataGrid1.Location = new System.Drawing.Point(0, 68);
            this.dataGrid1.Name = "dataGrid1";
            this.dataGrid1.Size = new System.Drawing.Size(292, 205);
            this.dataGrid1.TabIndex = 4;
            //
            // button1
            //
            this.button1.Location = new System.Drawing.Point(63, 20);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(75, 23);
            this.button1.TabIndex = 5;
            this.button1.Text = "Close";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click_1);
            //
            // ModalForm1
            //
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
            this.ClientSize = new System.Drawing.Size(292, 273);
            this.Controls.Add(this.button1);
            this.Controls.Add(this.dataGrid1);
            this.Name = "ModalForm1";
            this.Text = "ModalForm1";
            ((System.ComponentModel.ISupportInitialize)(this.dataGrid1)).EndInit();
            this.ResumeLayout(false);

            }
            #endregion

            private void cm_PositionChanged(object sender, EventArgs e)
            {
                  MessageBox.Show("cm_PositionChanged");
            }

            private void cm_CurrentChanged(object sender, EventArgs e)
            {
                  MessageBox.Show("cm_CurrentChanged");
            }

        private void button1_Click_1(object sender, EventArgs e)
        {
            this.DialogResult = DialogResult.OK;
        }
      }
}

[/Code]
0
 
LVL 11

Expert Comment

by:anyoneis
ID: 17035062
If the problem is the same as what you are having, you might want to "vote" on it at the MSDN Feedback center:

http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=155753

David
0
 
LVL 9

Expert Comment

by:WinterMuteUK
ID: 17035538
I haven't looked at the code you've posted thoroughly, but are you sure you're disposing correctly?
For example, are you disposing of the datatables / sets etc you are using as member vars?

Wint.
0
 
LVL 9

Expert Comment

by:WinterMuteUK
ID: 17035722
I think what .Net is doing with reference to raising the events is correct. The Datasource for the CurrencyManager is what is being changed, not the form, and the DataSet you are using still has a reference to the CurrencyManager.

There are a couple of things that you could do to your code to make it work the way you expect...

1.
You could pass a copy of the Dataset to your secondary form:

    DataSet copy = m_ds.Copy();
    ModalForm1 mf1 = new ModalForm1(copy);

Then you'll find that when the 'Button1' code exits from your Form1 that the ModalForm1 will be disposed properly and the events won't fire - this is because the 'DataSet' you are using is 'local' as well, and references to it are lost at the end of the Button1 code.

2.
Elevate the local variable 'cm' in the constructor of ModalForm1 to be a member variable and in the Dispose of the ModalForm1 you should -= from the events you += to in the constructor.


Wint.
0
 
LVL 11

Expert Comment

by:anyoneis
ID: 17038288
> DataSet you are using still has a reference to the CurrencyManager.

Really? I didn't know that. I would like to see some references on that, because that would explain a lot.

I disagree that you should have to unhook events when both the subscriber and the publisher are being destroyed.

But, in any case, I have a solution for VS2005. The BindingSource object has a Dispose method. If you bind your controls to BindingSource objects *and* ensure that these objects are disposed of when the form is closed (by putting them into the form's components collection, you will no longer have the problem. Evidently, BindingSource knows how to get itself unhooked.

I have modified my modal subform in this regard:

[Code]
using System;
using System.Data;      
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;

namespace TestModal
{
      /// <summary>
      /// Summary description for ModalForm1.
      /// </summary>
      public class ModalForm1 : System.Windows.Forms.Form
    {
            private System.Windows.Forms.DataGrid dataGrid1;
        private Button button1;
        private IContainer components;

        private BindingSource bs;

            public ModalForm1(DataSet ds)
            {
                  //
                  // Required for Windows Form Designer support
                  //
                  InitializeComponent();

            // if we have not added components to our form, there will be no
            // components collection, so we add it. This allows our BindingSource
            // to be disposed of properly when the form is disposed. If we add the
            // BindingSource using the toolbox, we don't have to do these next few lines
            if (this.components == null)
            {
                this.components = new System.ComponentModel.Container();
            }
            bs = new BindingSource(this.components);
           
            // Now, bind the BindingSource to the table
            bs.DataSource = ds;
            bs.DataMember = ds.Tables[0].TableName;

            dataGrid1.DataSource = ds;
            dataGrid1.DataMember = ds.Tables[0].TableName;

            bs.PositionChanged +=new EventHandler(bs_PositionChanged);
                  bs.CurrentChanged +=new EventHandler(bs_CurrentChanged);
            }

            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            protected override void Dispose( bool disposing )
            {
           
                  if( disposing )
                  {
                        if(components != null)
                        {
                              components.Dispose();
                        }
            }
                  base.Dispose( disposing );
            }

            #region Windows Form Designer generated code
            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InitializeComponent()
            {
            this.dataGrid1 = new System.Windows.Forms.DataGrid();
            this.button1 = new System.Windows.Forms.Button();
            ((System.ComponentModel.ISupportInitialize)(this.dataGrid1)).BeginInit();
            this.SuspendLayout();
            //
            // dataGrid1
            //
            this.dataGrid1.DataMember = "";
            this.dataGrid1.Dock = System.Windows.Forms.DockStyle.Bottom;
            this.dataGrid1.HeaderForeColor = System.Drawing.SystemColors.ControlText;
            this.dataGrid1.Location = new System.Drawing.Point(0, 81);
            this.dataGrid1.Name = "dataGrid1";
            this.dataGrid1.Size = new System.Drawing.Size(292, 192);
            this.dataGrid1.TabIndex = 4;
            //
            // button1
            //
            this.button1.Location = new System.Drawing.Point(63, 20);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(75, 23);
            this.button1.TabIndex = 5;
            this.button1.Text = "Close";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click_1);
            //
            // ModalForm1
            //
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
            this.ClientSize = new System.Drawing.Size(292, 273);
            this.Controls.Add(this.button1);
            this.Controls.Add(this.dataGrid1);
            this.Name = "ModalForm1";
            this.Text = "ModalForm1";
            ((System.ComponentModel.ISupportInitialize)(this.dataGrid1)).EndInit();
            this.ResumeLayout(false);

            }
            #endregion

            private void bs_PositionChanged(object sender, EventArgs e)
            {
                  MessageBox.Show("bs_PositionChanged");
            }

            private void bs_CurrentChanged(object sender, EventArgs e)
            {
                  MessageBox.Show("bs_CurrentChanged");
            }

        private void button1_Click_1(object sender, EventArgs e)
        {
            this.DialogResult = DialogResult.OK;
        }
      }
}

[/Code]
0
 
LVL 11

Expert Comment

by:anyoneis
ID: 17038630
Well, I muffed that! Change the code to assign the DataGrid datasource:

            //dataGrid1.DataSource = ds;
            //dataGrid1.DataMember = ds.Tables[0].TableName;
            dataGrid1.DataSource = bs;


David
0
 
LVL 9

Expert Comment

by:WinterMuteUK
ID: 17038758
Hi David,

>I disagree that you should have to unhook events when both the subscriber and the publisher are being destroyed.
I think you're missing the point, I agree that yes, the publisher and subscriber should be seperated when destroyed, however you  *aren't* destroying the publisher, you are still editing it. In terms of the ModalForm1 example you give the ModalForm1 cm variable *does* have a reference to the DataSource, as you have bound it to it. You aren't destroying the CurrencyManager.

You can see this by using the 'Copy' dataset solution I presented. When the ModalForm1 is disposed in that example, all the references are removed as the copy is local as well. The only difference between the copy and yours is that you are using a member variable that itself isn't destroyed, and in essence *This* is the publisher.

Wint.
0
 
LVL 11

Expert Comment

by:anyoneis
ID: 17039175
Sorry, I'm just not getting it. The fact that the Currency Manager has a reference to the DataSet does not affect it's lifetime. Someone must have a reference to the Currency Manager in order to keep it alive and kicking. Is it the Dataset?

Here is my limited understanding of what is entailed here:

Binding the DataGrid to the DataSet's DataTable creates an entry in the Modal Form's BindingContext. What is stored here is a reference to a CurrencyManager.

Subscribing to the CurrencyManager's events would create reference(s) from the CurrencyManager to the Form.

Destroying the form also destroys the forms BindingContext, which should also destroy any CurrencyManagers therein.

But, that is not happening. Someone is maintaining a reference to the CurrencyManager. But who?

David
0
 
LVL 9

Expert Comment

by:WinterMuteUK
ID: 17040853
I understand where you're coming from, and yes, I'm saying the DataSet or something there has a reference to the CurrencyManager, I also know why that seems wrong, and I'm not saying it is the correct answer - just a process of elimination is taking me there. If you use a copy of the dataset then all the problems go, whereas when you use the member they stay.

This implies that the DataSet has a reference to the currencymanager, (to me at least).

Erm, I know it doesn't really help as I can't definitively say if it does or doesn't.

Wint.
0

Featured Post

IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

Summary: Persistence is the capability of an application to store the state of objects and recover it when necessary. This article compares the two common types of serialization in aspects of data access, readability, and runtime cost. A ready-to…
Real-time is more about the business, not the technology. In day-to-day life, to make real-time decisions like buying or investing, business needs the latest information(e.g. Gold Rate/Stock Rate). Unlike traditional days, you need not wait for a fe…
This video discusses moving either the default database or any database to a new volume.
Access reports are powerful and flexible. Learn how to create a query and then a grouped report using the wizard. Modify the report design after the wizard is done to make it look better. There will be another video to explain how to put the final p…

705 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

19 Experts available now in Live!

Get 1:1 Help Now