As stated here the DataBindingComplete event for a DataGridView is fired whenever the contents of the data source change, or a property such as DataSource changes. This results in the method being called multiple times.
I am currently using the DataBindingComplete event to do some visual formatting to my form. For example, I make the text in the first column (column 0) appear as Row Headers and then hide that column (see code below).
private void grdComponents_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
foreach (DataGridViewRow row in grdComponents.Rows)
{
row.HeaderCell.Value = row.Cells[0].Value.ToString();
}
grdComponents.Columns[0].Visible = false;
// do more stuff...
}
It is unnecessary to execute this code more than once, and I am looking to put it into a place where that can happen. Unfortunately it didn't work when I added the snippet to the end of my form's Load method (after I set the DataSource of my DataGridView), nor did it work in the DataSourceChanged event.
Yes, you can use DataSourceChanged event, but be aware, that it occurs only when data source is changed. Additionally, DataBindingComplete offers you information why it has happend - through e.ListChangedType:
Reset = 0,// Much of the list has changed. Any listening controls should refresh all their data from the list.
ItemAdded = 1,// An item added to the list
ItemDeleted = 2,// An item deleted from the list.
ItemMoved = 3,// An item moved within the list.
ItemChanged = 4,// An item changed in the list.
PropertyDescriptorAdded = 5,// A System.ComponentModel.PropertyDescriptor was added, which changed the schema.
PropertyDescriptorDeleted = 6,// A System.ComponentModel.PropertyDescriptor was deleted, which changed the schema.
PropertyDescriptorChanged = 7// A System.ComponentModel.PropertyDescriptor was changed, which changed the schema.
According to this answer:
https://social.msdn.microsoft.com/forums/windows/en-us/50c4f46d-c3b8-4da7-b08f-a751dca12afd/databindingcomplete-event-is-been-called-twice
the whole thing happens because you don't have DataMember property set in your dataGridView. And you can set it only if you want to set particular table from database which is set as your DataSource of dataGridView. Other way - throws an exception.
The simplest way will be just to execute this code once:
Add a flag like Boolean isDataGridFormatted in your form.
And check it like
private void grdComponents_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
if (this.isDataGridFormatted )
return;
foreach (DataGridViewRow row in grdComponents.Rows)
{
row.HeaderCell.Value = row.Cells[0].Value.ToString();
}
grdComponents.Columns[0].Visible = false;
// do more stuff...
this.isDataGridFormatted = false;
}
A bit better will be to prepare your DataGridView during the form construction. As I understand your columns won't change during the course of your program but you don't want to initialize everything manually. You could load some dummy one-item(one-row) data during the initialization:
private void Initialize_DataGridView()
{
// Add dummy data to generate the columns
this.dataGridView_Items.DataContext = new Item[]{ new Item {Id = 5, Value = 6}};
// Make your formatting
foreach (DataGridViewRow row in grdComponents.Rows)
{
row.HeaderCell.Value = row.Cells[0].Value.ToString();
}
grdComponents.Columns[0].Visible = false;
// Reset the dummy data
this.dataGridView_Items.DataContext = null; // Or new Item[]{};
}
...
public MyForm()
{
Initialize();
this.Initialize_DataGridView();
}
I am not sure that exactly such code will work with dataGridView but it is close enough.
Of course an event would have been a nearly ideal solution but there's hardly any that deals with successful autogeneration of columns http://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview_events(v=vs.110).aspx except the AutoGenerateColumnChanged but that is not what we need.
While it is possible to use the ColumnAdded - it will probably execute only once foreach of the autogenerated column, the actual implementation could become an overkill and will be even less direct than already mentioned approaches.
If you will have some time and desire you could create your own DataGridView derived class, take Boolean isDataGridFormatted from your form and implement all the initialization(or event hooking) inside the custom DataGridView.
Related
I have a DataGridView binded with data from a database. I need to create a Form, which has input fields for data from a single grid row.
The Form has 30+ input controls - TextBoxes, Checkboxes and NumericUpDowns.
For now I went with this approach:
Retrieve current row from the DataGridView and load values from the cells to class instance
Pass the instance to the form and manually fill the input controls
Update the database from the form, update the DataGridView
I want to improve some things:
Is there any way to quickly fill all input controls from a class instance?
Is there any way to determine which input controls have changed their values besides manually subscribing every control on an event handler?
Is there any way to improve this whole thing, e.g. do something more efficiently?
If you are already passing in a DataRow, then you could instead pass in the DataTable and something that identifies the row in that table. And maybe optionally an adapter, if you want to commit the changes immediately on form exit. Then you can create a DataView of that table. And bind each edit control to a field in that view. Something like this:
public partial class EditForm : Form
{
DataRow row = null;
DataView view;
SqlDataAdapter adapter;
public EditForm(SqlDataAdapter adapter, DataTable table, int rowId)
{
InitializeComponent();
this.adapter = adapter;
view = table.DefaultView;
view.RowFilter = $"ID = {rowId}";
if (view.Count == 0) throw new Exception("no such row");
DataRowView dvr = view[0];
row = dvr.Row;
datebox.DataBindings.Add(new Binding("Value", view, "DATE"));
stringbox.DataBindings.Add(new Binding("Text", view, "O_STRING"));
this.FormClosing += EditForm_FormClosing;
}
private void EditForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (row.RowState == DataRowState.Modified) adapter.Update(new DataRow[] { row });
}
}
The above assuming that your table has key column named ID and fields DATE and O_STRING.
This will save you the trouble of creating an intermediate custom class instance, based on that row, moving values to and from various objects and automatically sets the RowStatae in the original table.
Re: value changed indicators. Not sure if there is a really elegant way to do that. Firstly, if I had to, I would change the background (or foreground) color rather than font boldness. Setting font to bold would change the width of the content and that is usually quite annoying. Then, I would add handlers to the TextChanged events (or ValueChange events, for controls that are not text-based). You dont need to write custom handlers to each and every edit control - in the event handler, you get the object sender parameter that points to the control object. Then you can get the field name binded to that control with something like this:
private void stringbox_TextChanged(object sender, EventArgs e)
{
Control ctrl = (Control)sender;
string fieldName = ctrl.DataBindings[0].BindingMemberInfo.BindingMember;
if ((string)view[0].Row[fieldName] != ctrl.Text) ctrl.BackColor = Color.Pink;
}
That way you will only need to add a TextChanged handler once (per edit contol class), not one per every edit box you have.
I have created an unbound column which I intend to populate with calculated data; however, I am unable to get the CustomUnboundColumnData event to fire. I basically copied the code from DevExpress documentation on https://documentation.devexpress.com/#WindowsForms/CustomDocument1477
As per other posts found I made sure that there are no other columns with the same name.
The new unbound column does indeed appear in the grid, but the event is never firing so I don't know how to populate it.
In my constructor I define the columns as below: (I made sure there are no other columns with the same name)
GridColumn testColumn = new GridColumn();
testColumn.FieldName = "Test Column1";
testColumn.VisibleIndex = gridView1.Columns.Count;
testColumn.UnboundType = DevExpress.Data.UnboundColumnType.DateTime;
// Disable editing.
testColumn.OptionsColumn.AllowEdit = true;
// Specify format settings.
testColumn.DisplayFormat.FormatType = DevExpress.Utils.FormatType.DateTime;
testColumn.DisplayFormat.FormatString = "d";
testColumn.Visible = true;
gridView1.Columns.Add(testColumn);
Then I have my event function which never fires
private void gridView1_CustomUnboundColumnData(object sender, CustomColumnDataEventArgs e)
{
MessageBox.Show("unbound column a go go");
How can I populate the unbound column?
Solved thanks to DevExpress support forum (which is very nice by the way)
I was missing the line for gridView1.CustomUnboundColumnData += gridView1_CustomUnboundColumnData; which tells the grid which event handler(s) to use for unbound columns
I'm not new to WinForms but I have always "rolled my own" when it comes to validation - and I think it's time to take advantage of the built-in stuff. I Googled around for some basics but I'm not finding what I need...
I've got a DataGridView. I have a (custom) object that has four String properties. I am getting a List<> of them from an XML file.
So when I do this:
dgv.DataSource = genericListOfStationObjects;
the rows do show up correctly in the DataGridView. So the databinding is working just fine - at least in the "incoming" direction.That's good.But what I need to do is:
track IsDirty on each row (without manually adding a flag?)
visually indicate (within the DataGridView) if any of the values in the DataGridView's cells are invalid. (I have validation methods on my custom object (of which the List<> is comprised.) I cannot get those "error glyphs" to show up. I've tried all the SO posts I could find on that...
Thank you very much,Eliezer
To answer your first question there are two native properties that you will want to utilize:
IsCurrentCellDirty
IsCurrentRowDirty
These only work for the currently selected cell/row. So you may have to resort to making your own if you need to track "IsDirty" on each row. But there is an event that comes with the native dirty settings, CurrentCellDirtyStateChanged you can utilize to log all of the changes. You could also use CellValueChanged to log any changes you might need. I personally use a form-level property to keep track if I have any data edits/changes by using either of these events and if I do, I save the edits before closing the form.
Data validation is fairly straightforward on the WinForms DataGridView. To get the red error glyphs to show simply set the ErrorText of a cell or row. I also utilize the DataGridView's native RowValidating event as well.
void dg_RowValidating(object sender, DataGridViewCellCancelEventArgs e)
{
DataGridViewRow dgRow = dg.Rows[e.RowIndex];
if ((dgRow.Cells["yourColumnName"].Value == null) ||
(dgRow.Cells["yourColumnName"].Value.ToString().Length == 0))
{
// Set both the row and cell error text at the same time.
dgRow.ErrorText = dgRow.Cells["dgTxtColTest List"].ErrorText =
"You must enter a value in the " + yourColumnName + " column."
e.Cancel = true;
}
}
When the row is validated you must clear any error messages you might have created above:
void dg_RowValidated(object sender, DataGridViewCellEventArgs e)
{
// Clear errors from row header and cells in the row
DataGridViewRow row = dg.Rows[e.RowIndex];
row.ErrorText = ""; // Clear the row header error text
// Clear all error texts from the row
foreach (DataGridViewCell cell in row.Cells)
{
cell.ErrorText = ""; // Clear each cell in the row as now row is valid
}
}
I have a DataGridView on a form that is databound to a list of objects. I have several column including a checkbox column. The requirement is that only one item in the collection may have the databound boolean property set to true at a given time. The data object is named Interval, the Property in question is Program:
public bool Program
{
get { return _program; }
set
{
if (value)
{
Parent.Intervals.Except(new[] { this }).ForEach(interval => interval.Program = false);
}
_program = value;
OnPropertyChanged();
}
}
My expected behavior would be that clicking the databound checkbox would set the Program property for one instance of Interval to true, and in so doing, would set the Program property on all other instances to false. That is indeed what happens, however the datagridview does not update correctly. It will leave previous checkboxes checked and after tabbing off the cell or mousing over another checkbox, then it will update that particular checkbox.
How can I get the datagrid view to accurately show the state of my objects. I trusted you, DataGridView! You lied to me. I... I can never trust anything you say again.
The solution is to use a BindingList instead. When binding to a regular List or other collection (that is not of a Binding specific bent), only the selected rows values are updated. BindingList, however, has the added benefit that properties from all of the items are updated, not just the selected Row. Keep in mind that if you are using something like AddRange (from List) you may have to modify your code to add items either at initialization time or via a loop, as BindingList does not have an AddRange method (although an extension method could be written).
Try this
private void dgv_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if ((bool)dg.Rows[e.RowIndex].Cells[CheckBoxColumnIndex].Value)
{
var rows = (from DataGridViewRow row in Clients.Rows
where row.Index != e.RowIndex
select row).ToArray();
foreach(DataGridViewRow row in rows)
{
row.Cells[CheckBoxColumnIndex].Value = false;
}
}
}
I want an OpenFileDialog to come up when a user clicks on a cell, then display the result in the cell.
It all works, except that the DataGridView displays an extra row, for adding values to the list it's bound to. The row shows up if dataGridView.AllowUserToAddNewRows == true, which is what I want. What I don't want is for the application to crash when that row is edited programatically; instead, it should do exactly what it would do if the user had edited that row manually (add the new row to the underlying list, push another empty row onto the grid for adding values).
I read about SendKeys.Send(), which should make the DataGridView behave exactly as though the user had typed the value in; however, it does not work either. Here is what I am trying:
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
dataGridView1.CurrentCell = cell;
//simply doing a cell.Value = etc. will cause the program to crash
cell.ReadOnly = false;
dataGridView1.Columns[cell.ColumnIndex].ReadOnly = false;
dataGridView1.EditMode = DataGridViewEditMode.EditOnEnter;
dataGridView1.BeginEdit(true);
SendKeys.Send(openFileDialog1.FileName + "{Enter}");
dataGridView1.EndEdit();
cell.ReadOnly = true;
dataGridView1.Columns[cell.ColumnIndex].ReadOnly = true;
}
//I would expect the FileName would be in the cell now, and a new empty
//row tacked onto the end of the DataGridView, but it's not; the DataGridView
//is not changed at all.
I found a workaround on this page, though I don't know why it works
public MyForm()
{
InitializeComponent();
//Create a BindingSource, set its DataSource to my list,
//set the DataGrid's DataSource to the BindindingSource...
_bindingSource.AddingNew += OnAddingNewToBindingSource;
}
private void OnAddingNewToBindingSource(object sender, AddingNewEventArgs e)
{
if(dataGridView1.Rows.Count == _bindingSource.Count)
{
_bindingSource.RemoveAt(_bindingSource.Count - 1);
}
}
I'm getting very sick of spending so much time dealing with Visual Studio bugs...
I was having the same problem when trying to programattically edit cells with a binding source.
""Operation is not valid due to the current state of the object"
Which operation? What State? So helpful.
My code seem to work fine except when editing the last row in the grid.
Turns out the key is DataGridView.NotifiyCurrentCelldirty(true)
The correct sequence for programatically editing a cell, so it works the same as if the user did it.
(A new empty row appears when changing a cell in the last row) is something like this:
1) Make the cell to edit the current cell (do what ever you need to the current currentcell, first
like calling endEdit if it is in edit mode.)
2) Call DataGridview.BeginEdit(false)
3) Call DataGridView.NotifyCurrentCellDirty(true)
4) Modify the value.
5) Call DataGridView.EndEdit()
And you'll want to do something for the RowValidating and RowValidated events.
One of my routines for updating a cell value looks like this:
This is from a method in my class derived from DataGridView.
You could do the same thing from the containing form, calling
through a DataGridView instance, because the methods are public.
Here the calls are using an impliciit 'this.'
private void EnterTime()
{
if (CurrentRow == null) return;
SaveCurrentCell(); // Calls EndEdit() if CurrentCell.IsInEditMode
DataGridViewCell previous = CurrentCell;
CurrentCell = CurrentRow.Cells[CatchForm.TimeColumn];
BeginEdit(false);
NotifyCurrentCellDirty(true);
CurrentCell.Value = DateTime.Now;
EndEdit();
CurrentCell = previous;
}
I’m not sure why a separate call is needed.
Why doesn’t BeginEdit, or actually modifying the cell value, cause the right
things to happen?
And if you move the NotifyCurrentCellDirty call to after you actually modify the cell,
it doesn’t behave correctly either. All very annoying.
This is old, but I am running VS2010 and just come across this issue. I have a DataGridView bound to a List<T> using a BindingList<T>. I have a drag n' drop event on my DataGridView and it would throw this exception after deleting all rows from the DGV (except for the last blank one which one cannot delete) and then adding new rows to the DGV in the DragDrop handler via the BindingList<T>. This exception was not thrown if I simply added rows manually editing individual cells.
One solution I read said to handle the BindingList<T>.AddNew event, but I found that this event did not fire when calling BindingList<T>.Add() within the DragDrop event handler (I'm not sure why). I solved the issue by adding
if(bindingList.Count == 0)
bindingList.RemoveAt(0)
inside of the DragDrop event handler before adding new objects to bindingList. It seemed that adding an object to the bindingList failed when the only "object" in the bindingList was the one associated to the final blank row. The point of a BindingList<T> is to allow the developer to work with it instead of the DGV directly, but it seems doing so can cause problems in border cases.
The relationship between DGV rows and BindingList<T> rows seems to be a bit of a gray area. I have not spent much time investigating this, but it is not clear to me what is the state of the "object" in the BindingList<T> associated to the final (empty) row of the DGV. However, it does seem like the "object" at the end is only instantiated "correctly" when you interact with the final row directly (not via a DataSource).
Try this:
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
int row = e.RowIndex;
int clmn = e.ColumnIndex;
if(e.RowIndex == dataGridView1.Rows.Count- 1)
dataGridView1.Rows.Add();
dataGridView1.Rows[row].Cells[clmn].Value = openFileDialog1.FileName;
}
EDIT
I didn't notice that you are binding your datagridview :(
Ok, to solve it: use binding source, set its DataSource property to your list, then set the data source of the data grid view to this binding source. Now, the code should look like so:
public partial class frmTestDataGridView : Form
{
BindingSource bindingSource1 = new BindingSource();
List<string> datasource = new List<string>();
public frmTestDataGridView()
{
InitializeComponent();
datasource.Add("item1");
datasource.Add("item2");
datasource.Add("item3");
bindingSource1.DataSource = datasource;
dataGridView1.DataSource = bindingSource1;
}
private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)
{
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
int row = e.RowIndex;
int clmn = e.ColumnIndex;
if (e.RowIndex == dataGridView1.Rows.Count - 1)
{
bindingSource1.Add("");
}
dataGridView1.Rows[row].Cells[clmn].Value = openFileDialog1.FileName;
}
}
}
Remember to use Row.BeginEdit() and Row.EndEdit() if you get this error while editing a value in a row, using DataGrid or GridEX from Janus (in my case). The sample code that Darrel Lee posted here (https://stackoverflow.com/a/9143590/1278771) remind me to use these instructions that I forgot to use and this solved the problem for me.