2

I created a form with two controls dataGridView1 and button1.

public partial class Form1 : Form
{
    public List<Foo> ds { get; private set; }

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        ds = new List<Foo> {
            new Foo { A="abc" }, new Foo{B="bbb"}
        };
        dataGridView1.DataSource = new BindingList<Foo>(ds);
    }

    private void button1_Click(object sender, EventArgs e)
    {
        var foo = (Foo)dataGridView1.Rows[0].DataBoundItem;
        foo.B = "1BBB";
        foreach(DataGridViewRow x in dataGridView1.Rows)
        {
            if (x.DataBoundItem != null)
                ((Foo)x.DataBoundItem).C = "CCC";
        }
    }
}

However, clicking button1 doesn't show the changed values in the form? Debugging in Visual Studio shows the values have been set.


Update:

I changed the code to the following to use DataTable. However, clicking the button still doesn't update the UI?

public partial class Form1 : Form
{
    public List<Foo> ds { get; private set; }

    private Dictionary<string, Foo> dict;

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        //ds = new List<Foo> {
        //    new Foo { A="abc" }, new Foo{B="bbb"}
        //};
        //dataGridView1.DataSource = new BindingList<Foo>(ds);
        var dt = new DataTable();
        dt.Columns.Add(new DataColumn { DataType = typeof(string), ColumnName = "A", Unique = true });
        dt.Columns.Add(new DataColumn { DataType = typeof(string), ColumnName = "B", Unique = false });
        dt.Columns.Add(new DataColumn { DataType = typeof(string), ColumnName = "C", Unique = false });
        dt.Columns.Add(new DataColumn { DataType = typeof(string), ColumnName = "D", Unique = false });
        dt.Columns.Add(new DataColumn { DataType = typeof(string), ColumnName = "E", Unique = false });
        dt.Columns.Add(new DataColumn { DataType = typeof(string), ColumnName = "F", Unique = false });
        dt.Columns.Add(new DataColumn { DataType = typeof(string), ColumnName = "G", Unique = false });
        dt.Columns.Add(new DataColumn { DataType = typeof(string), ColumnName = "H", Unique = false });
        dt.Columns.Add(new DataColumn { DataType = typeof(string), ColumnName = "I", Unique = false });
        dt.Columns.Add(new DataColumn { DataType = typeof(string), ColumnName = "J", Unique = false });
        dt.Columns.Add(new DataColumn { DataType = typeof(string), ColumnName = "K", Unique = false });
        dt.Columns.Add(new DataColumn { DataType = typeof(string), ColumnName = "L", Unique = false });
        dt.PrimaryKey = new DataColumn[] { dt.Columns["A"] };

        var row = dt.NewRow();
        row["A"] = "1";
        dt.Rows.Add(row);
        row = dt.NewRow();
        row["A"] = "2";
        dt.Rows.Add(row);
        row = dt.NewRow();
        row["A"] = "3";
        dt.Rows.Add(row);
        dataGridView1.DataSource = dt;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        var foo = (DataRowView)dataGridView1.Rows[0].DataBoundItem;
        foo["B"] = "1BBB";
        //foreach (DataGridViewRow x in dataGridView1.Rows)
        //{
        //    if (x.DataBoundItem != null)
        //        ((Foo)x.DataBoundItem).C = "CCC";
        //}
        //dataGridView1.DataSource = ds;
    }
}
ca9163d9
  • 27,283
  • 64
  • 210
  • 413

2 Answers2

3

Add "EndEdit" after you change the value:

private void button1_Click(object sender, EventArgs e)
    {
        var foo = (DataRowView)dataGridView1.Rows[0].DataBoundItem;
        foo["B"] = "1BBB";
        foo.EndEdit();
    }

You can also change the source to binding source and then work directly on the table.

Yair I
  • 1,133
  • 1
  • 6
  • 9
  • Do you know why `DataBoundItem` can be casted to `DataRowView` when binding to a `DataTable` but not `List`? – ca9163d9 Oct 07 '19 at 03:09
1

You need to refresh the bindings of the datasource because you change directly the bounded instance and not the cell.

Try this:

  foreach ( DataGridViewRow x in dataGridView1.Rows )
  {
    if ( x.DataBoundItem != null)
      ((Foo)x.DataBoundItem ).C = "CCC";
  }
  dataGridView1.DataSource = new BindingList<Foo>(ds);

Else you can write instead:

  x.Cells[2].Value = "CCC";

So the grid is updated as well as the bounded instance.

You don't need a BindingList and you can write this, the first time, and after a modification:

dataGridView1.DataSource = null;
dataGridView1.DataSource = ds;

This is a better pattern:

How can I refresh c# dataGridView after update ?

But we may use a DataTable, or better a DataSet having a DataTable while using the Visual Studio designers and using drag and drop of the table on the forms to write less code and automaticaly generate the datagrid and all non-visual data components.

Here is a summary

To refresh the view of a modified DataBoundItem that is an object of a DataSource that is a BindingList or simply a List, we can reassign the DataSource.

But if it's a BindingSource you can use ResetBindings on the BindingSurce.

But if you use a DataTable instead of a DataSource on a List then you can use EndEdit on the DataBoundItem that is a DataRowView.

Then the question is what does it is best to do here: use a BindingSource on a List or create a DataTable and manage the LoadFromStream and SaveFromStream, since you said that the data come from a IEnumerable<> stream?

Therefore you can see CopyToDataTable() to map your List<Foo> to a DataTable:

Creating a DataTable From a Query (LINQ to DataSet)

  • Will `dataGridView1.DataSource = new BindingList(ds);` be expensive? The list may be big and I will need to update the list every second. I read that updating the bound data source will update the control too in another SO question answer. – ca9163d9 Oct 06 '19 at 03:14
  • Perhaps if you have lots of data. But you don't need to do that as explained. You may prefer manipulate the grid cells. You can also write `dataGridView1.DataSource = ds.ToList();`and `dataGridView1.DataSource = ds;` which is less expensive as it refresh the bindings with only one assignment. Or create a datatable to have auto-refresh. –  Oct 06 '19 at 03:17
  • And using a strongly-typed dataset is better: you add a new item in the project that is a dataset and you open it and you can design your data structures or drop database items on it. It is as simple as that. –  Oct 06 '19 at 03:30
  • I may need to fill the data source from a stream. May need to use Reactive framework. Is strongly typed dataset still a better choise? – ca9163d9 Oct 06 '19 at 03:34
  • It depens of what you want to do and what data you need to manage. For a real app, yes, you should use at least a strongly-typed dataset, or EF if you have a database behind and if you want a big machinery. Dataset is fine. –  Oct 06 '19 at 03:34
  • How about strongly typed `List`? I probably don't need to design the data structure since I already have the domain entity. – ca9163d9 Oct 06 '19 at 03:37
  • For a stream data source, yes, you may use a strongly-typed dataset. First you create your dataset at design time. Then you code the loading/populating data in the dataset as well as the saving to the stream if needed. Then you create a datasource for the dataset and use it on the control. Using a List you have not all the features of the Dataset. And as you seen, you have some problems if you change the data directly in the list. With a datatable (by code) or a dataset (by design) that have datatables you have bidirectionnal changes propagating. Using a dataset, VS do things you need for you. –  Oct 06 '19 at 03:42
  • Where are the data? What do you mean by a stream? –  Oct 06 '19 at 03:45
  • I'm trying to change `List` to `DataTable` in the code. However, it's non-generic type. – ca9163d9 Oct 06 '19 at 03:47
  • Why do you want generic? Try to use a strongly-typed dataset and you'll see how it is simple and elegant. –  Oct 06 '19 at 03:48
  • The data will be generated from another service, e.g. `IEnumerable GetStream(...)`. – ca9163d9 Oct 06 '19 at 03:52
  • No problem: you will populate by foreach on the list and add rows in the datatable. Using a dataset you don't need to code the datatable and you will have strongly-typed data structure. –  Oct 06 '19 at 03:53
  • Habit to use the generic version for type safety. Now I'm defining the 12 columns by calling `dataTable.Columns.Add(new DataColumn { .... }, ...... ); – ca9163d9 Oct 06 '19 at 03:54
  • With datasource on strongly-typed datatable of a strongly-typed dataset your grid will be fine and you'll be able to modify cells or items as you want, it will auto-refresh. And less code is to write. You will gain time. Try and you will like it. You will save tons of lines to write and it will be as robust as clean. –  Oct 06 '19 at 03:55
  • Or you can use the `dataGridView1_DataSourceChanged` event raised on `((Foo)x.DataBoundItem ).C = "CCC";` to manually update the cell. –  Oct 06 '19 at 04:08
  • Found that, it may help you using a simple list using a bindingsource instead of a bindinglist and the resetbinding: https://stackoverflow.com/questions/7008361/how-can-i-refresh-c-sharp-datagridview-after-update. But if your app is a for production app, prefer dataset. Your choice depends of the data, the quantity and the finality. –  Oct 06 '19 at 04:12
  • Just updated the question with `DataTable`. However, it still doesn't work? – ca9163d9 Oct 06 '19 at 04:14
  • Got it. The linked question works. The key is `BindingSource.ResetBindings(false)`. – ca9163d9 Oct 06 '19 at 04:25
  • Yes. The bindingsource is generated by the VS form designer when using strongly-typed dataset. You create DataSet, draw the datatable or import it from a database, then you drag/drop the datatable on the form and it generates the grid and all components to manage the data. You code nothing. It's magic. But in your case, if I understand, you will need to create a LoadInTheRows and SaveFromTheRows data from/to the stream. The question is: is it interesting? –  Oct 06 '19 at 04:31
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/200462/discussion-on-answer-by-olivier-rogier-cannot-update-datagridview-cell-via-datab). – Samuel Liew Oct 06 '19 at 10:05
  • I think I will use DataTable/EndEdit because it will not refresh the data grid view and the screen redisplay with the first row on top. Unfortunately, the DataBoundItem can only be cast to `DataRowView` when using DataTable. – ca9163d9 Oct 06 '19 at 21:51