OK、少し面白くなりましたが、ようやく解決しました。重要なのは、ハンドラーで追加および変更イベントを処理し、RowChanging
ハンドラーで削除イベントを処理することでしたRowDeleted
。次の人が数時間頭を悩ませるのを防ぐのに十分なコードを提示します。
以下のコードでは、_dataSet は DataAdapter を介して入力された DataSet です。_dataTable は_dataSet.Tables[0].DefaultView
. _dataTable は、XAML で ItemsSource として 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 にレコードが追加されました。以下に示す 3 つのヘルパー関数は、適切な状態のレコードと、Current/Original/Proposed メンバーの正しい列情報を含む DataTable を返しました。
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;
}
}
}
エラー表示が確実に機能するようになりました。
対処する必要がある最後の問題は、自動生成されたインデックス値に関するものです。自動生成されたインデックスを持つテーブルがある場合、そのフィールドや他のフィールドに別の値を入力して、レコードをコミットすることができます (タブで移動するか、戻る)。グリッド ビューを更新すると、他のフィールドが変更されていることがわかりますが、キーは初期値を保持しています。他のすべての行 (任意の、おそらく大きな数) を取得/更新することなく、そのレコードを取得/再表示する方法を理解する必要があります。
この取り組みにより、標準のエスケープ シーケンスで期待される編集のキャンセル動作が保持されます。つまり、レコードの検証が失敗した場合、最初は現在のセル編集をキャンセルします。2 番目は、行の編集をキャンセルします。
共有して楽しんでください!
編集: XAML で使用される検証規則とコード ビハインドを追加して、堅牢なエラー表示を取得しました。このような長い回答で申し訳ありません。もし私が最初にこれらすべてを理解していたら、その方法を提示するためにより適切なフォーラムを選んだでしょう。