4
votes

I have a problem when assigning the DataSource propery on a DataGridView control. My DataSource is a DataTable's DefaultView and, as expected, columns are automatically created in the DataGridView to match those in the DataTable when I assign it.

What happens next is that the columns seem to be automatically removed and recreated a further 2 times by the DataGridView. Why would this happen?

In a form's constructor:

//A DataTable is created with 5 columns
//The DataTable is populated with some rows.

myDgv.AutoGenerateColumns = true;
myDgv.DataSource = myDataTable.DefaultView;
// myDgv.ColumnAdded event is fired 5 times.
// WHY: myDgv.ColumnRemoved event is fired 5 times.
// WHY: myDgv.ColumnAdded event is fired 5 times.
// WHY: myDgv.ColumnRemoved event is fired 5 times.
// WHY: myDgv.ColumnAdded event is fired 5 times.

Edit: Added a (hopefully) self contained example. If I set breakpoints in the event handlers, I hit the 'Added' one 6 times and the 'Removed' one 4 times. The DataTable contains 2 columns and I never ask for any columns to be removed in my code.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace asdasdgf
{
    public class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            DataTable dt = new DataTable();
            dt.Columns.Add("Col1", typeof(int));
            dt.Columns.Add("Col2", typeof(string));

            foreach (int i in Enumerable.Range(0, 10))
            {
                var row = dt.NewRow();
                row["Col1"] = i;
                row["Col2"] = "stackoverflow";
                dt.Rows.Add(row);
            }

            dataGridView1.ColumnAdded += new DataGridViewColumnEventHandler(dataGridView1_ColumnAdded);
            dataGridView1.ColumnRemoved += new DataGridViewColumnEventHandler(dataGridView1_ColumnRemoved);

            dataGridView1.DataSource = dt.DefaultView;
        }

        void dataGridView1_ColumnRemoved(object sender, DataGridViewColumnEventArgs e)
        {
            // Break here
        }

        void dataGridView1_ColumnAdded(object sender, DataGridViewColumnEventArgs e)
        {
            // Break here
        }

        // Form1.Designer.cs contents:
        #region Windows Form Designer generated code
        private System.ComponentModel.IContainer components = null;
        private System.Windows.Forms.DataGridView dataGridView1;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }



        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.dataGridView1 = new System.Windows.Forms.DataGridView();
            ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();
            this.SuspendLayout();
            // 
            // dataGridView1
            // 
            this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
            this.dataGridView1.Location = new System.Drawing.Point(12, 41);
            this.dataGridView1.Name = "dataGridView1";
            this.dataGridView1.Size = new System.Drawing.Size(240, 150);
            this.dataGridView1.TabIndex = 0;
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(284, 264);
            this.Controls.Add(this.dataGridView1);
            this.Name = "Form1";
            this.Text = "Form1";
            ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
            this.ResumeLayout(false);

        }

        #endregion
    }
}
5
What happens when you use a BindingSource instead?lc.
I added a (hopefully) self contained example. If I set breakpoints in the event handlers, I hit the 'Added' one 6 times and the 'Removed' one 4 times. The DataTable contains 2 columns and I never ask for any columns to be removed in my code.xyz

5 Answers

2
votes

I have an app that uses a DataGridView to display the contents of a DataTable, but I link them like this:

dataGridView.DataSource = dataTable;

Can you try this?

EDIT:

I also have the following in the auto generated code:

this.dataGridView.EditMode = System.Windows.Forms.DataGridViewEditMode.EditProgrammatically;

Not sure why this would make a difference though.

1
votes

A few months ago at work, we were building a Grid control that would have built in filtering capabilities (similar to excel). Initially we used a datagridview and built around that. This issue you're brining up now was the number one biggest thorn in our side! Since we were using a DGV underneath, and hooking into its events to trigger various things we needed to do, it was an absolute nightmare. The DGV is a great control, but under the covers it does some wonky sh**!! The best workaround at the end of the day was to auto generate the columns ourselves. Yea, it was a pain but at least we had full control.

In our final version though, we ended up scratching that all together, and went with the inheritance route. We inherited from the DGV and it made our lives ALOT easier. Now, I'm not sure what you're trying to accomplish here, but if you're building your own grid, try inheritance first! As for the answer to your question, you're not doing anything wrong. The datagridview is just kooky like that. If you're not building a control around the DGV, and just need those events, I say try to stay away from column added / removed. See if you can instead use bindingcompleted instead.

1
votes

If myDgv.DataSource is alreadys set to something that would explain at least some of the Add/Remove actions. Dito for when the columns are already present design-time.

So make sure you grid is empty and the DataSource property is cleared in the designer.

Additional:

I ran the sample code and can confirm it but you can reduce (halve) the madness by moving the code from the constructor to a normal Form_Load event. That is the proper way anyhow, I see far too many people confusing the ctor and Load.

But then there still are 2 Add and 1 Remove actions per column, I'm afraid we'll have to subscribe that to some DataGrid inefficiency. (Somewhere in the DataSource.set())

Additional 2:

Apparently the AutoGenerateColumns is true by default and is not directly settable from the properties window. I cleared it by setting (and later clearing) a Datasource at Design time. And then, with a cleared AutoGenerateColumns property,

dataGridView1.AutoGenerateColumns = true;
dataGridView1.DataSource = dt.DefaultView;

Only yields the two Add events.

1
votes

After a lot of fiddling, this seems to be the way to go.

In your form's Load event handler:

dgv.AutoGenerateColumns = false;
dgv.DataSource = myDataSource;

In your form's Shown event handler:

dgv.AutoGenerateColumns = true;

Giving the desired result of the ColumnAdded event being fired once for each column, and the ColumnRemoved event not being fired at all. (If AutoGenerateColumns = true is set in the Load event handler, it will do this annoying Add-Remove-Add dance.)

1
votes

I got a solution ! Select the form or user control and change the value of Localizable property to true. Edit the resx file and suppress UserAddedColumn entries. Then select your datagrid and choose "modify columns". Suppress all columns that were added automatically. Reset the value of Localizable to false. Doing so the automatically added columns never came back on my grid.

Maybe you need to save the form/user control and close the design tab between each operation.