好的,它有点有趣,但我终于解决了。关键是处理处理程序中的添加和修改事件以及RowChanging
处理程序中的删除事件RowDeleted
。我将提供足够的代码来节省下一个人几个小时的头疼。
在下面的代码中,_dataSet 是一个通过 DataAdapter 填充的 DataSet。_dataTable 是_dataSet.Tables[0].DefaultView
. _dataTable 作为 ItemsSource 绑定到 XAML 中的 DataGrid。此代码在我的 ViewModel 中,但它也可能在 Model 代码中。我把它剪掉了一点,所以可能需要对其进行调整才能在代码中为你工作。
private void AttachDataTableEvents()
{
_dataTable.RowChanging += new DataRowChangeEventHandler(DataTable_RowChanging);
_dataTable.RowChanged += new DataRowChangeEventHandler(DataTable_RowChanged);
_dataTable.RowDeleting += new DataRowChangeEventHandler(DataTable_RowDeleting);
_dataTable.RowDeleted += new DataRowChangeEventHandler(DataTable_RowDeleted);
}
private void DataTable_RowChanging(object sender, DataRowChangeEventArgs e)
{
Trace.WriteLine(string.Format("DataTable_RowChanging(): Action {0}, RowState {1}", e.Action, e.Row.RowState));
if (e.Action == DataRowAction.Add)
{
e.Row.ClearErrors();
DataTable updateDataTable = CreateUpdateDataTableForRowAdd(_dataSet, 0, e.Row);
int rowsAffected;
string errorMessage;
if (!UpdateTableData(updateDataTable, out rowsAffected, out errorMessage))
{
e.Row.RowError = errorMessage;
throw new ArgumentException(errorMessage);
}
}
else if (e.Action == DataRowAction.Change)
{
e.Row.ClearErrors();
DataTable updateDataTable = CreateUpdateDataTableForRowChange(_dataSet, 0, e.Row);
int rowsAffected;
string errorMessage;
if (!UpdateTableData(updateDataTable, out rowsAffected, out errorMessage))
{
e.Row.RowError = errorMessage;
throw new ArgumentException(errorMessage);
}
}
}
private void DataTable_RowChanged(object sender, DataRowChangeEventArgs e)
{
Trace.WriteLine(string.Format("DataTable_RowChanged(): Action {0}, RowState {1}", e.Action, e.Row.RowState));
if (e.Action == DataRowAction.Add)
{
e.Row.AcceptChanges();
}
else if (e.Action == DataRowAction.Change)
{
e.Row.AcceptChanges();
}
}
private void DataTable_RowDeleting(object sender, DataRowChangeEventArgs e)
{
Trace.WriteLine(string.Format("DataTable_RowDeleting(): Action {0}, RowState {1}", e.Action, e.Row.RowState));
// can't stop the operation here
}
private void DataTable_RowDeleted(object sender, DataRowChangeEventArgs e)
{
Trace.WriteLine(string.Format("DataTable_RowDeleted(): Action {0}, RowState {1}", e.Action, e.Row.RowState));
DataTable updateDataTable = CreateUpdateDataTableForRowDelete(_dataSet, 0, e.Row);
int rowsAffected;
string errorMessage;
if (!UpdateTableData(updateDataTable, out rowsAffected, out errorMessage))
{
e.Row.RejectChanges();
Mediator mediator = _iUnityContainer.Resolve<Mediator>();
mediator.NotifyColleagues<string>(MediatorMessages.NotifyViaModalDialog, errorMessage);
}
else
{
e.Row.AcceptChanges();
}
}
关键是用要更新的记录创建一个新的 DataTable。然后将此 DataTable 传递给 DataAdapter.Update(dataTable) 方法。对于添加/更改/删除事件,创建了 DataSet 模式的克隆,然后以正确的状态将记录添加到 DataTable。下面显示的三个辅助函数返回了一个 DataTable,其中记录处于适当的状态,并且在 Current/Original/Proposed 成员中具有正确的列信息。
private static DataTable CreateUpdateDataTableForRowAdd(DataSet originalDataSet, int originalDataTableIndex, DataRow addedDataRow)
{
DataSet updateDataSet = originalDataSet.Clone();
DataTable updateDataTable = updateDataSet.Tables[originalDataTableIndex];
DataRow dataRow = updateDataTable.NewRow();
int columnCount = updateDataTable.Columns.Count;
for (int i = 0; i < columnCount; ++i)
{
dataRow[i] = addedDataRow[i, DataRowVersion.Proposed];
}
updateDataTable.Rows.Add(dataRow);
// dataRow state is *Added*
return updateDataTable;
}
private static DataTable CreateUpdateDataTableForRowChange(DataSet originalDataSet, int originalDataTableIndex, DataRow changedDataRow)
{
DataSet updateDataSet = originalDataSet.Clone();
DataTable updateDataTable = updateDataSet.Tables[originalDataTableIndex];
DataRow dataRow = updateDataTable.NewRow();
int columnCount = updateDataTable.Columns.Count;
for (int i = 0; i < columnCount; ++i)
{
dataRow[i] = changedDataRow[i, DataRowVersion.Original];
}
updateDataTable.Rows.Add(dataRow);
dataRow.AcceptChanges();
dataRow.BeginEdit();
for (int i = 0; i < columnCount; ++i)
{
dataRow[i] = changedDataRow[i, DataRowVersion.Proposed];
}
dataRow.EndEdit();
// dataRow state is *Modified*
return updateDataTable;
}
private static DataTable CreateUpdateDataTableForRowDelete(DataSet originalDataSet, int originalDataTableIndex, DataRow deletedDataRow)
{
DataSet updateDataSet = originalDataSet.Clone();
DataTable updateDataTable = updateDataSet.Tables[originalDataTableIndex];
DataRow dataRow = updateDataTable.NewRow();
int columnCount = updateDataTable.Columns.Count;
for (int i = 0; i < columnCount; ++i)
{
dataRow[i] = deletedDataRow[i, DataRowVersion.Original];
}
updateDataTable.Rows.Add(dataRow);
dataRow.AcceptChanges();
dataRow.Delete();
// dataRow state is *Deleted*
return updateDataTable;
}
如果实现了上面的代码,则行为几乎是正确的。看到的问题是当您离开记录时验证失败。它第一次工作时,即错误标记显示在行标题上。但是,如果您像编辑一样移入记录但不更改任何值,然后再次移开,则错误指示器会消失。但是,在移回行并取消编辑之前,您仍然无法移动到网格中的另一个单元格。
为了使该行为正确,您需要为网格添加验证规则:
<DataGrid Grid.Column="1" Grid.Row="1" AutoGenerateColumns="True" ItemsSource="{Binding TableDataView}" Name="_gridTableGrid" CanUserDeleteRows="True" CanUserAddRows="True" RowHeaderWidth="25" CanUserResizeRows="False">
<DataGrid.RowValidationRules>
<local:DataGridRowValidationRule ValidationStep="CommittedValue" />
</DataGrid.RowValidationRules>
</DataGrid>
然后,在代码隐藏中,添加以下内容:
public class DataGridRowValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
BindingGroup bindingGroup = (BindingGroup)value;
if (bindingGroup.Items.Count > 0)
{
System.Data.DataRowView dataRowView = bindingGroup.Items[0] as System.Data.DataRowView;
if (dataRowView.Row.HasErrors)
{
string errorMessage = string.IsNullOrWhiteSpace(dataRowView.Row.RowError) ? "There is an unspecified error in the row" : dataRowView.Row.RowError;
return new ValidationResult(false, errorMessage);
}
else
{
return ValidationResult.ValidResult;
}
}
else
{
return ValidationResult.ValidResult;
}
}
}
现在错误指示工作稳健。
最后一个需要处理的问题是围绕自动生成的索引值。如果存在具有自动生成索引的表,则可以在该字段以及其他字段中输入不同的值,然后提交记录(将其关闭或返回)。如果刷新网格视图,我们将看到其他字段已更改,但键仍保留其初始值。我必须弄清楚如何检索/重新显示该记录,而不必检索/刷新所有其他行(任意且可能很大)。
这种努力导致保留了通过标准转义序列预期的取消编辑行为。即,如果记录验证失败,首先取消当前单元格编辑;第二个取消行编辑。
分享和享受!
编辑:我添加了 XAML 中使用的验证规则和代码隐藏以获得可靠的错误指示。抱歉这么长的回答。如果我一开始就弄清楚了这一切,我会选择一个更合适的论坛来介绍这种方法。