A simple WinForms app to learn about control data binding. Code below from a Form named frmMain and containing TextBoxes txtFirstName and txtLastName, and a numeric up/down named nudRow. Editing the text in the text boxes and changing the row, I notice the previous DataRow has values changed in the 'proposed' row version (eg _Tbl.Rows[3][1, DataRowVersion.Proposed]), but the RowState is still 'Unchanged'. What am I missing that would make 'proposed' become 'current' and change the RowState? The commented out 'DataSourceUpdateMode' didn't make any difference.
I'm also pretty fuzzy on why I need to keep resetting the TextBox DataBindings instead of just setting the DataRowView (_CurRow) in the numeric up/down value changed event.
EDIT: I thought I was pretty clear that my question was: Why didn't the RowState change? Additionally, is there a standard pattern (validation or something) typically used to process the 'proposed' value and change the RowState? As an aside, I was wondering why I had to rebuild the data binding instead of just changing the value of the thing that's already bound, but maybe that needs to be a separate question.
EDIT2: After looking at some documentation on DataRowVersion.Proposed, it looks like I should add code something like below to my numeric up/down value changed event. Unfortunately, I can't answer my own question while the post is on hold...
if (_CurRow.Row.HasVersion(DataRowVersion.Proposed))
{
// Do validation stuff then
if(isValid)
{
_CurRow.Row.EndEdit();
}
else
{
_CurRow.Row.CancelEdit();
}
}
Personally, I would validate each user input in the form input control, but I am now aware that even reverting original values in the controls would create a proposed version in the DataRow.
private DataRowView _CurRow = null;
private DataTable _Tbl = null;
private const string COL_NAME_ROW = "Row No.";
private const string COL_NAME_FIRST = "First Name";
private const string COL_NAME_LAST = "Last Name";
public frmMain()
{
DataRow dr;
InitializeComponent();
_Tbl = new DataTable();
_Tbl.Columns.Add(COL_NAME_ROW, typeof(int));
_Tbl.Columns.Add(COL_NAME_FIRST, typeof(string));
_Tbl.Columns.Add(COL_NAME_LAST, typeof(string));
dr = _Tbl.NewRow();
dr[COL_NAME_ROW] = 0;
dr[COL_NAME_FIRST] = "Alan";
dr[COL_NAME_LAST] = "Ladd";
_Tbl.Rows.Add(dr);
dr = _Tbl.NewRow();
dr[COL_NAME_ROW] = 1;
dr[COL_NAME_FIRST] = "Boris";
dr[COL_NAME_LAST] = "Yeltsin";
_Tbl.Rows.Add(dr);
dr = _Tbl.NewRow();
dr[COL_NAME_ROW] = 2;
dr[COL_NAME_FIRST] = "Cab";
dr[COL_NAME_LAST] = "Calloway";
_Tbl.Rows.Add(dr);
dr = _Tbl.NewRow();
dr[COL_NAME_ROW] = 3;
dr[COL_NAME_FIRST] = "David";
dr[COL_NAME_LAST] = "Letterman";
_Tbl.Rows.Add(dr);
_Tbl.AcceptChanges();
}
private void frmMain_Shown(object sender, EventArgs e)
{
_CurRow = _Tbl.DefaultView[0];
this.txtFirstName.DataBindings.Add("Text", _CurRow, COL_NAME_FIRST); //,
//false, DataSourceUpdateMode.OnPropertyChanged);
this.txtLastName.DataBindings.Add("Text", _CurRow, COL_NAME_LAST); //,
//false, DataSourceUpdateMode.OnPropertyChanged);
//this.nudRow.DataBindings.Add("Value", _Tbl, COL_NAME_ROW);
}
private void nudRow_ValueChanged(object sender, EventArgs e)
{
// Previous row has modified (proposed) data here, but unchanged RowState
_CurRow = _Tbl.DefaultView[(int)this.nudRow.Value];
this.txtFirstName.DataBindings.Clear();
this.txtFirstName.DataBindings.Add("Text", _CurRow, COL_NAME_FIRST); //,
//false, DataSourceUpdateMode.OnPropertyChanged);
this.txtLastName.DataBindings.Clear();
this.txtLastName.DataBindings.Add("Text", _CurRow, COL_NAME_LAST); //,
//false, DataSourceUpdateMode.OnPropertyChanged);
}
Editing the bound control causes a BeginEdit on the DataRow which should be closed with either an EndEdit or a CancelEdit. The code below is modified with one possible solution.
private DataRowView _CurRow = null;
private DataTable _Tbl = null;
private const string COL_NAME_ROW = "Row No.";
private const string COL_NAME_FIRST = "First Name";
private const string COL_NAME_LAST = "Last Name";
public frmMain()
{
DataRow dr;
InitializeComponent();
_Tbl = new DataTable();
_Tbl.Columns.Add(COL_NAME_ROW, typeof(int));
_Tbl.Columns.Add(COL_NAME_FIRST, typeof(string));
_Tbl.Columns.Add(COL_NAME_LAST, typeof(string));
dr = _Tbl.NewRow();
dr[COL_NAME_ROW] = 0;
dr[COL_NAME_FIRST] = "Alan";
dr[COL_NAME_LAST] = "Ladd";
_Tbl.Rows.Add(dr);
dr = _Tbl.NewRow();
dr[COL_NAME_ROW] = 1;
dr[COL_NAME_FIRST] = "Boris";
dr[COL_NAME_LAST] = "Yeltsin";
_Tbl.Rows.Add(dr);
dr = _Tbl.NewRow();
dr[COL_NAME_ROW] = 2;
dr[COL_NAME_FIRST] = "Cab";
dr[COL_NAME_LAST] = "Calloway";
_Tbl.Rows.Add(dr);
dr = _Tbl.NewRow();
dr[COL_NAME_ROW] = 3;
dr[COL_NAME_FIRST] = "David";
dr[COL_NAME_LAST] = "Letterman";
_Tbl.Rows.Add(dr);
_Tbl.AcceptChanges();
}
private void frmMain_Shown(object sender, EventArgs e)
{
_CurRow = _Tbl.DefaultView[0];
this.txtFirstName.DataBindings.Add("Text", _CurRow, COL_NAME_FIRST); //,
//false, DataSourceUpdateMode.OnPropertyChanged);
this.txtLastName.DataBindings.Add("Text", _CurRow, COL_NAME_LAST); //,
//false, DataSourceUpdateMode.OnPropertyChanged);
//this.nudRow.DataBindings.Add("Value", _Tbl, COL_NAME_ROW);
}
private void nudRow_ValueChanged(object sender, EventArgs e)
{
bool? hasChanged = null;
// Previous row has modified (proposed) data here, but unchanged
// RowState. Need to end or cancel the BeginEdit caused by
// editing the bound control in order to update the RowState to
// Modified and push Proposed value to Current (EndEdit) or
// leave the RowState Unchanged (CancelEdit)
hasChanged = this.RowHasChanged(_CurRow.Row);
if (!hasChanged.HasValue)
{
// Didn't have proposed version so no-op
}
else if (hasChanged.Value)
{
_CurRow.Row.EndEdit();
}
else
{
_CurRow.Row.CancelEdit();
}
_CurRow = _Tbl.DefaultView[(int)this.nudRow.Value];
this.txtFirstName.DataBindings.Clear();
this.txtFirstName.DataBindings.Add("Text", _CurRow, COL_NAME_FIRST);
this.txtLastName.DataBindings.Clear();
this.txtLastName.DataBindings.Add("Text", _CurRow, COL_NAME_LAST);
}
// This is necessary because reverting values still causes
// row to have proposed version, but isn't a true change
private bool RowHasChanged(DataRow DataRowObj)
{
bool retVal = false;
if (!DataRowObj.HasVersion(DataRowVersion.Proposed)) return retVal;
for (int ii = 0; ii < _CurRow.Row.ItemArray.Length; ii++)
{
if (!object.Equals(
_CurRow.Row[ii, DataRowVersion.Current],
_CurRow.Row[ii, DataRowVersion.Proposed]))
{
retVal = true;
break;
}
}
if (!retVal.HasValue) retVal = false;
return retVal;
}