Want to protect your cyber security and still get fast solutions? Ask a secure question today.Go Premium

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 618
  • Last Modified:

Cleaning up Event Handlers and Delegates?

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
directxBOB
Asked:
directxBOB
  • 10
  • 6
3 Solutions
 
anyoneisCommented:
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
 
directxBOBAuthor Commented:
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
 
anyoneisCommented:
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
Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

 
anyoneisCommented:
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
 
WinterMuteUKCommented:

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
 
anyoneisCommented:
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
 
WinterMuteUKCommented:
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
 
anyoneisCommented:
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
 
anyoneisCommented:

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
 
anyoneisCommented:
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
 
WinterMuteUKCommented:
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
 
WinterMuteUKCommented:
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
 
anyoneisCommented:
> 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
 
anyoneisCommented:
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
 
WinterMuteUKCommented:
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
 
anyoneisCommented:
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
 
WinterMuteUKCommented:
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

Receive 1:1 tech help

Solve your biggest tech problems alongside global tech experts with 1:1 help.

  • 10
  • 6
Tackle projects and never again get stuck behind a technical roadblock.
Join Now