What is the best way to refresh a DataGridView when you update an underlying data source?
I'm updating the datasource frequently and wanted to display the outcome to the user as it happens.
I've got something like this (and it works), but setting the DataGridView.DataSource to null doesn't seem like the right way.
List<ItemState> itemStates = new List<ItemState>();
dataGridView1.DataSource = itemStates;
for (int i = 0; i < 10; i++) {
itemStates.Add(new ItemState { Id = i.ToString() });
dataGridView1.DataSource = null;
dataGridView1.DataSource = itemStates;
System.Threading.Thread.Sleep(500);
}
I ran into this myself. My recommendation: If you have ownership of the datasource, don't use a List. Use a BindingList. The BindingList has events that fire when items are added or changed, and the DataGridView will automatically update itself when these events are fired.
Well, it doesn't get much better than that. Officially, you should use
dataGridView1.DataSource = typeof(List);
dataGridView1.DataSource = itemStates;
It's still a "clear/reset source" kind of solution, but I have yet to find anything else that would reliably refresh the DGV data source.
The cleanest, most efficient and paradigm-friendly solution in this case is to use a System.Windows.Forms.BindingSource as a proxy between your list of items (datasource) and your DataGridView:
var itemStates = new List<ItemState>();
var bindingSource1 = new System.Windows.Forms.BindingSource { DataSource = itemStates };
dataGridView1.DataSource = bindingSource1;
Then, when adding items, use Add() method of BindingSource instead of your list's Add() method:
for (var i = 0; i < 10; i++)
{
bindingSource1.Add(new ItemState { Id = i.ToString() });
System.Threading.Thread.Sleep(500);
}
This way you adding items to your list and notifying DataGridView about those additions with the same line of code. No need to reset DataGridView's DataSource every time you make a change to the list.
It also worth mentioning that you can drop a BindingSource onto your form directly in Visual Studio's Forms Designer and attach it as a data source to your DataGridView there as well, which saves you a line of code in the above example where I'm doing it manually.
Observablecollection :Represents a dynamic data collection that provides notifications
when items get added, removed, or when the whole list is refreshed.
You can enumerate over any collection that implements the IEnumerable interface.
However, to set up dynamic bindings so that insertions or deletions in the
collection update the UI automatically,
the collection must implement the INotifyCollectionChanged interface.
This interface exposes the CollectionChanged event,
an event that should be raised whenever the underlying collection changes.
Observablecollection<ItemState> itemStates = new Observablecollection<ItemState>();
for (int i = 0; i < 10; i++) {
itemStates.Add(new ItemState { Id = i.ToString() });
}
dataGridView1.DataSource = itemStates;
This is copy my answer from THIS place.
Only need to fill datagrid again like this:
this.XXXTableAdapter.Fill(this.DataSet.XXX);
If you use automaticlly connect from dataGridView this code create automaticlly in Form_Load()
You are setting the datasource inside of the loop and sleeping 500 after each add. Why not just add to itemstates and then set your datasource AFTER you have added everything. If you want the thread sleep after that fine. The first block of code here is yours the second block I modified.
for (int i = 0; i < 10; i++) {
itemStates.Add(new ItemState { Id = i.ToString() });
dataGridView1.DataSource = null;
dataGridView1.DataSource = itemStates;
System.Threading.Thread.Sleep(500);
}
Change your Code As follows: this is much faster.
for (int i = 0; i < 10; i++) {
itemStates.Add(new ItemState { Id = i.ToString() });
}
dataGridView1.DataSource = typeof(List);
dataGridView1.DataSource = itemStates;
System.Threading.Thread.Sleep(500);
Just delete all Rows and fill it after deleting:
BindingList<ItemState> itemStates = new BindingList<ItemState>();
datagridview1.Rows.Clear();
for(int i = 0; i < 10; i++)
{
itemStates.Add(new ItemState { id = i.ToString() });
}
datagridview1.DataSource = itemStates;
Thread.Sleep(500);
Try this Code
List itemStates = new List();
for (int i = 0; i < 10; i++)
{
itemStates.Add(new ItemState { Id = i.ToString() });
dataGridView1.DataSource = itemStates;
dataGridView1.DataBind();
System.Threading.Thread.Sleep(500);
}
Related
I am developing a software and i need different form of a DataGridView.
I created them and inserted them into an array with this method:
private DataGridView[] cloneDataGridViews(int posCount, DataGridView dataGridView)
{
List<DataGridView> dataGridViewList = new List<DataGridView>();
for(int i=0;i<posCount;i++)
{
DataGridView dgv = new DataGridView();
dgv = dataGridView;
dataGridViewList.Add(dgv);
}
return dataGridViewList.ToArray();
}
And I am trying to show them with this code:
void GridViewSelectorLoad(object sender, System.EventArgs e)
{
this.AutoScroll = true;
int startY = 30;
for(int i=0;i<dataGridViewArray.Length;i++)
{
int height = dataGridViewArray[i].Height;
int posY = startY + 10 + i*height;
Panel pnl = new Panel();
pnl.Controls.Add(dataGridViewArray[i]);
dataGridViewArray[i].Parent = pnl;
pnl.Location = new Point(100,posY);
pnl.Name = "pnl"+i.ToString();
pnl.Height = dataGridViewArray[i].Height;
pnl.Width = dataGridViewArray[i].Width;
pnl.Parent = this;
this.Controls.Add(pnl);
}
}
But it shows just one datagridview, how can I show all of them?
What is wrong with that code?
A control can only have one parent, but you're trying to set the same DataGridView as a child of multiple Panels.
for(int i=0;i<posCount;i++)
{
DataGridView dgv = new DataGridView();
dgv = dataGridView; // not creating a new instance of DataGridView
dataGridViewList.Add(dgv);
}
Here's the relevant part of the Controls.Add() method that causes this behavior.
if (value.parent != null)
{
value.parent.Controls.Remove(value);
}
You've got a single instance of DataGridView that you add to each new Panel. Each time, it's Parent property is set to the latest Panel. Then when you try adding it to the next Panel, the code above removes it from the previous one.
If you create a new instance inside the loop, it works fine. You'll need to copy over those values from the existing DataGridView that you wish to have in each new DataGridView instance.
for(int i=0; i<posCount; i++)
{
DataGridView dgv
= new DataGridView
{
Name = dataGridView.Name,
DataSource = dataGridView.DataSource,
...
};
dataGridViewList.Add(dgv);
}
cloneDataGridViews is not cloning the datagrid, its adding the same instance multiple times:
dgv = dataGridView;
It has been pointed out that all you are copying in your code is the same old reference to the same DGV into each slot of your list. Instead you will have to copy both the column structure and the values of all cells like this:
public DataGridView cloneDataGridView(DataGridView oldDGV)
{
DataGridView newDGV = new DataGridView();
foreach (DataGridViewCell cell in oldDGV.Rows[0].Cells)
newDGV.Columns.Add(new DataGridViewColumn(cell));
newDGV.Rows.Add(oldDGV.Rows.Count);
for (int row = 0; row < oldDGV.Rows.Count; row++)
for (int col = 0; col < oldDGV.Columns.Count; col++)
newDGV[col, row].Value = oldDGV[col, row].Value;
return newDGV;
}
You could call it like this:
for(int i=0;i<posCount;i++)
{
DataGridView dgv = cloneDataGridView(dataGridView);
dataGridViewList.Add(dgv);
}
Note: This piece of code assumes that there is at least one row in the source DGV.
I have a CheckedListBox and I would like to check all the items that are in another List.
This code does not work since the CheckedItems property is read-only and the types do not match, but it gives the best idea of what I want to do.
checkedListBox1.DataSource = DataSetSelectAll().Tables[0];
checkedListBox1.ValueMember = "id_table";
checkedListBox1.DisplayMember = "name";
List<tableClass> list = MyCheckedList();
checkedListBox1.CheckedItems = list;
I know this is wrong but do not know how to explain it better.
Its not possible to set(check) many items at a time like this, checkedListBox1.CheckedItems = list;
better you can use for loop like:
List<tableClass> list = MyCheckedList();
for (int count = 0; count < checkedListBox1.Items.Count; count++)
{
if (list.Contains(checkedListBox1.Items[count].ToString()))
{
checkedListBox1.SetItemChecked(count, true);
}
}
andy's answer is right but I have an easier solution. My solution works in windows application.
DataTable dt = MyCheckedList();
foreach (DataRow dr in dt.Rows)
{
for (int i = 0; i < checkedListBox1.Items.Count; i++)
{
if (dr["valueMember"].ToString() == ((DataRowView)checkedListBox1.Items[i])[0].ToString())
{
checkedListBox1.SetItemChecked(i, true);
}
}
}
Note: dt must fill with a dataTable which has all checkedList Values.
I'm not sure why, but I SetItemChecked(index, tf) wasn't giving me what I wanted. This is how I solved it - explicitly setting the CheckedState.
for (int i = 0; i < myCheckedListBox.Items.Count; i++)
{
if (boolList[i])
{
myCheckedListBox.SetItemCheckState(i, CheckState.Checked);
} else
{
myCheckedListBox.SetItemCheckState(i, CheckState.Unchecked);
}
}
I am trying to do something like this:
for (int i = 1; i < nCounter ; i++)
{
string dvName = "dv" + i.ToString();
System.Windows.Forms.DataGridView dvName = new DataGridView();
// other operations will go here..
}
As you can guess, what I am trying to do is at i == 1, create a DataGridView with name dv1, and at i == 2, create a DataGridView with name dv2, but I can't.
Visual studio squiggles saying "a local variable named dvName is already delared in this scope" I also tried the following:
for (int i = 1; i <nCounter ; i++)
{
System.Windows.Forms.DataGridView dv & i = new DataGridView();
// other operations will go here..
}
But VS squiggles again, I hope you understood what I am trying to accomplish. Can anyone suggest how can I do this?
What you really need is a Dictionary<int, DataGridView> grids. Populate it in your for loop (grids[i] = new DataGridView();) and then, later, use the required grid (grids[someCalculatedIndex])
Hope this helps.
try a data structure where you can hold your variables eg dict etc
System.Collections.Generic.Dictionary<string,System.Windows.Forms.DataGridView>
grids = new Dictionary<string,System.Windows.Forms.DataGridView>();
for (int i = 1; i <nCounter ; i++)
{
grids.Add("dv" + i.ToString(), new DataGridView());
}
// to work on grid 1
DataGridView grid1 = grids["dv1"];
// so on
So your are trying to create the variable name dynamically? That's not possible. Why not use an Array or a List (or even a Dictionary)? Or do you want to just set the name of the control?
var list = new List<DataGridView>();
for (int i = 1; i <nCounter ; i++)
{
System.Windows.Forms.DataGridView dvName = new DataGridView();
dvName.Name = "dv" + i.ToString();
list.Add(dvName);
// other operations will go here..
}
foreach (var dv in list)
{
...do something...
}
DataGridView secondDv = list.Single(dv=>dv.Name == "dv2");
secondDv.DoSomething()
Not clear want you want to do...
my DataTable has over 1000 columns and I want to display values on the datagridview. Because of the FillWeigth problem I use the following method to fill the gridview,
public bool TransferDataTableToGrid(DataGridView dataGrid, DataTable dataTable)
{
dataGrid.SuspendLayout();
if ((dataGrid != null) && (dataTable != null))
{
dataGrid.Columns.Clear();
dataGrid.AutoGenerateColumns = false;
dataGrid.DataSource = dataTable;
for (int i = 0; i < dataTable.Columns.Count; i++)
{
DataGridViewColumn column = new DataGridViewColumn();
column.Name = dataTable.Columns[i].ColumnName;
column.FillWeight = 1;
column.CellTemplate = new DataGridViewTextBoxCell();
column.ValueType = dataTable.Columns[i].DataType;
dataGrid.Columns.Add(column);
}
for (int i = 0; i < dataTable.Columns.Count; i++)
{
for (int ii = 0; ii < dataTable.Rows.Count; ii++)
{
dataGrid[i, ii].Value = dataTable.Rows[ii][i];
}
}
}
dataGrid.ResumeLayout();
return true;
}
and sometimes I have an effect that my gridview is empty. Only after second execution data is displayed. Do you have any ideas, why...?
Thanks.
I recommend to use paging, i mean that you can show about 20 columns with navigation buttons
under your grid, it's like Google or others... even your are not programming a web application.
Use binding source to fill your grid
SqlDataAdapter adapter = new SqlDataAdapter(database.cmd);
dataSet1.Tables.Clear();
adapter.Fill(dataSet1, "Table");
bs = new BindingSource();
bs.DataSource = dataSet1.Tables["Table"];
dataGridView1.DataSource = bs;
now you dont need to worry about creating columns and fill cells in loops and its much better performance
Bind Data to Datagridview
Well, I solved my problem. With Ivan's suggestion I tried the alternative way to fill data: instead of using DataSource I add new rows manually
foreach (DataRow row in dataTable.Rows)
{
var dataGridRow = new DataGridViewRow();
dataGridRow.CreateCells(dataGrid);
for (int i = 0; i < row.ItemArray.Length; i++)
{
dataGridRow.Cells[i].Value = row.ItemArray[i];
}
dataGrid.Rows.Add(dataGridRow);
}
...and it works - data in dgv is displayed. Thanks!
In my datagridview, I bind a List of objects named 'ProductLine'. But unfortunately with this approach I cannot 'Add' or 'Delete' rows to the datagridview in edit mode. When I create a fresh new order I can Add or Delete rows but once I save it and try to open it in Edit then it doesn't let me 'Add' or 'Delete' (via keyboard).
Any idea?
Here is the code for this:
If it is a fresh Order then I do something like this:
private void Save(){
for (int i = 0; i <= dtgProdSer.RowCount - 1; i++)
{
if ((itemList != null) && (itemList.Count > i))
productLine = itemList[i];
else
productLine = new ProductLine();
productLine.Amount = Convert.ToDouble(dataGridViewTextBoxCell.Value);
}
}
And if it is an Edit then in the Form_Load I check ProductId is NON Zero then I do the following:
private void fillScreen{
dtgProdSer.DataSource = itemList;
}
But with this I cannot Add or Delete Rows in Edit mode.
Any advise is greatly appreciated.
You haven't shown what type itemList is, but I'm going to assume that its not an ObservableCollection. In that case, you need to wrap your list in a binding source:
var list = new List<ProductLine>(5);
list.Add(new ProductLine { Amount = list.Count });
list.Add(new ProductLine { Amount = list.Count });
list.Add(new ProductLine { Amount = list.Count });
list.Add(new ProductLine { Amount = list.Count });
list.Add(new ProductLine { Amount = list.Count });
var bs = new BindingSource {DataSource = list };
dataGridView1.DataSource = bs;
FWIW, this is a well covered issue. Most of your DataGridView questions will probably be answered qucker by skimming the Related section on the right of the screen. For new questions, searching SO is a good first step.