views:

4253

answers:

3

I have a DataTable which is populated from a CSV file then, using a DataGridView the data is edited in memory. As far as I understand the programmatic editing of the data should be done on the DataTable where the user editing is done via. the DataGridView.

However when I add columns programmatically to the DataTable, it is not reflected automatically in the DataGridView and I suspect the converse is also true.

How do you keep the two concurrent? I thought the idea of data binding was that this was automatic ...

Here is the relevant setup code - WorksheetGridView subclasses DataGridView


// Can access data directly
public DataTable data = new DataTable();

public WorksheetGridView()
{
    InitializeComponent();

    // Allow copying from table to clipboard
    this.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableWithoutHeaderText;

    // TODO: how to allow both row and column selects?
    //this.SelectionMode = DataGridViewSelectionMode.ColumnHeaderSelect;

    // Load up a blank DataTable to hold user inputted data
    int i;
    const int numBlankRows = Config.Application.DefaultNumRows;
    const int numBlankCols = Config.Application.DefaultNumCols;
    // TODO: Figure out how to include this as a config variable
    DBNull dfltCellContent = DBNull.Value;
    DataRow tmpRow;
    // Add columns - i used for naming
    for (i = 0; i < numBlankCols; i++)
    {
        this.AddColumn(i);
    }

    // Add rows
    for (i = 0; i < numBlankRows; i++)
    {
       tmpRow = this.data.NewRow();
       // Fill cells with something (i.e. blank cells)
       foreach (DataColumn col in this.data.Columns)
       {
           tmpRow[col.ColumnName] = DBNull.Value;
       }
       this.data.Rows.Add(tmpRow);
    }
    // Link data to the view
    this.DataSource = this.data;
}

private void AddColumn(int colIndex)
{
    // Adds a column to the data array
    DataColumn tmpCol;
    tmpCol = new DataColumn();
    tmpCol.DataType = Type.GetType(Config.Application.DataType);
    tmpCol.ColumnName = "C" + colIndex.ToString();
    tmpCol.ReadOnly = false;
    tmpCol.Unique = false;
    tmpCol.AllowDBNull = true;
    this.data.Columns.Add(tmpCol);
}

The bit that doesn't work is later when I call AddColumn for instance in this code to handle pasting tab-delimited data,


        public void PasteToCells(string mode)
        {
            // TODO: Write paste code
            int i;
            if (Clipboard.ContainsText())
            {
                string clipBoardContent = Clipboard.GetText();
                using (CsvReader pastedCsvReader = new CsvReader(
                    new StringReader(clipBoardContent), false, '\t'))
                {
                    // TODO: If more rows/cols than in data table then expand accordingly
                    int numPastedCols = pastedCsvReader.FieldCount;
                    int currRowIndex, currColumnIndex;
                    GetSelectedInsertPoint(out currRowIndex, out currColumnIndex);
                    // Make space for columns if needed
                    if (mode == "insertcols")
                    {
                        int baseIndex = this.data.Columns.Count;
                        for (i = 0; i < numPastedCols; i++)
                        {
                            //this.Columns.Add
                            // TODO: Doesn't work yet - do you edit the DataGridView and reflect to DataTable or vise-versa?
                            this.AddColumn(baseIndex + i);

                        }
                    }
                    while  (pastedCsvReader.ReadNextRecord()) 
                    {
                        // Make space for rows (row by row) if needed
                        if (mode == "insertrows")
                        {
                            this.data.NewRow();
                            // TODO: Add a row
                        }
                        // Populate the cells with valid pasted text
                        for (i = 0; i < numPastedCols; i++)
                        {
                            this.data.Rows[currRowIndex][currColumnIndex + i] = ConvertCellContent(pastedCsvReader[i]);
                        }
                        currRowIndex = currRowIndex + 1;

                    }



                }

            }
            else
            {
                // TODO: Do nothing?
                Console.WriteLine("No text on clipboard");
            }
        }

I tried the example given below and it does work. However when I try and do this the horizontal scroll bar expands and contracts briefly but the table in the subclassed DataGridView is not updated. The column is however in the DataTable though - for example I cannot add a column of the same name, the column count reflects the extra columns too. Is there perhaps a designer option that might constrict the updating of the DataGridView?

Also adding rows works fine.

SOLVED:

The AutoGeneratedColumns was set to false in the designer code despite being true in the properties dialog (and set explicitly in the code). The initial columns were generated programmatically and so should not have appeared however this was not picked up on since the designer code also continued to generate 'designed in' columns that were originally used for debugging.

Moral: Check the autogenerated code!

In addition to this, see this post and this post

+1  A: 

Is AutoGenerateColumns set to true? If you are making changes after the initial Binding you'll also have to call DataBind() to rebind the changed datasource. I know this is true for an AJAX callback, I think it is true for a WinForms control PostBack.

Mark Brittingham
AutoGenerateColumns is true
Brendan
+2  A: 

This doesn't sound right. To test it out, I wrote a simple app, that creates a DataTable and adds some data to it. On the button1 click it binds the table to the DataGridView. Then, I added a second button, which when clicked, adds another column to the underlying DataTable. When I tested it, and I clicked the second button, the grid immedialtey reflected the update. To test the reverse, I added a third button which pops up a dialog with a DataGridView that gets bound to the same datatable. At runtime, I then added some values to the first datagridview, and when I clicked the button to bring up the dialog, the changes were reflected.

My point is, they are supposed to stay concurrent. Mark may be right when he suggested you check if AutoGenerateColumns is set to true. You don't need to call DataBind though, that's only for a GridView on the web. Maybe you can post of what you're doing, because this SHOULD work.

How I tested it:

        DataTable table = new DataTable();
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            table.Columns.Add("Name");
            table.Columns.Add("Age", typeof(int));
            table.Rows.Add("Alex", 26);
            table.Rows.Add("Jim", 36);
            table.Rows.Add("Bob", 34);
            table.Rows.Add("Mike", 47);
            table.Rows.Add("Joe", 61);

            this.dataGridView1.DataSource = table;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            table.Columns.Add("Height", typeof(int));
            foreach (DataRow row in table.Rows)
            {
                row["Height"] = 100;
            }
        }

        private void button3_Click(object sender, EventArgs e)
        {
            GridViewer g = new GridViewer { DataSource = table };
            g.ShowDialog();
        }

        public partial class GridViewer : Form //just has a DataGridView on it
        {
            public GridViewer()
            {
            InitializeComponent();
            }

            public object DataSource
            {
                get { return this.dataGridView1.DataSource; }
                set { this.dataGridView1.DataSource = value; }
            }
        }
BFree
In particular, check the AutoGenerateColumns value in the designer code. Answer selected since the example helped a great deal in debugging!
Brendan
A: 

I had the same issue and I issued a DataBind(). It's not the silver bullet for everything, but it's what helped me in a few cases. I had to put it in before capturing information through a DataView, after the EditCommand and UpdateCommand events immediately after the EditItemIndex statement,

protected void datalistUWSolutions_EditCommand(object source, DataListCommandEventArgs e)
{
  datalistUWSolutions.EditItemIndex = e.Item.ItemIndex;
  datalistUWSolutions.DataBind(); // refresh the grid.
}

and

protected void datalistUWSolutions_UpdateCommand(object source, DataListCommandEventArgs e)
{
  objDSSolutions.UpdateParameters["Name"].DefaultValue = ((Label)e.Item.FindControl("lblSolutionName")).Text;
  objDSSolutions.UpdateParameters["PriorityOrder"].DefaultValue = ((Label)e.Item.FindControl("lblOrder")).Text;
  objDSSolutions.UpdateParameters["Value"].DefaultValue = ((TextBox)e.Item.FindControl("txtSolutionValue")).Text;
  objDSSolutions.Update();
  datalistUWSolutions.EditItemIndex = -1; // Release the edited record
  datalistUWSolutions.DataBind();         // Redind the records for refesh the control
}
SnapJag
The DataGridView that's part of System.Windows.Forms does not have a DataBind method. You're thinking of the GridView that's part of System.Web.UI.WebControls. The OP is talking about WinForms.
BFree
So it is. Thanks for clarifying that. Great job answering the question.
SnapJag