1

WPFDataGridをDataTableにバインドしています。データベース内の任意のテーブルから基になるDataSetに入力します。DataTableRowChangingRowChangedイベントの両方に添付しました。ユーザーが行を変更すると、これらのイベントが発生し、行を検証できるようになります。

DataGridから最適な動作を取得するには、メッセージを設定し、イベントハンドラーe.Row.RowErrorから例外をスローする必要があることは明らかです。RowChanging行ヘッダーにエラーとして行をマークするxamlがあるので、それが表示され、エラーメッセージを含む優れたツールチップが表示されます。最適とは、検証が説明されているように処理されるときに、これらのグリッドで期待されるエスケープシーケンスが期待どおりに機能することを意味します。イベントから同じ検証を実行しようとするとRowChanged、編集が適切にロールバックされないファンキーな動作が発生します。

RowChanging私が抱えている問題は、すべてのDB検証ルールが適用され、他のユーザーの変更との衝突をハンドラーで検出できるように、基になるDataSetを更新する必要があることです。アクションが失敗した場合は、説明されているように検証にフラグを立てることができます。ただし、はe.Row.RowStateUnchangedとして受信され、含まれているDataSetをDB更新メソッドに渡すと、そのDataAdapter.Update(myDataTable)メソッドは行が変更されたと見なさないため、何もしません。RowChangedこの動作は、ハンドラーで同じことをしたときに起こることとは対照的です。その時点で、レコード(Current / Original / Proposed)の値が更新され、レコードに変更のマークが付けられます。

その時点でのDataAdapterの更新により、データベースアクティビティが発生します。しかし、失敗した場合のイベントシーケンスの間違った時点にいます。エラーにフラグを立てることはできますが、グリッドのロールバック動作が正しく機能しません(通常、変更されたセルがロールバックされない結果になります)。

私の質問は、データベースが更新されるように、どのようにしてレコード(またはレコードのコピー??)を変更済み状態で取得するのかということです。私は通常、型指定されたDataSetを使用しますが、今回は任意のテーブルを追跡しているため、DataSetを使用しています。

4

2 に答える 2

1

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 で使用される検証規則とコード ビハインドを追加して、堅牢なエラー表示を取得しました。このような長い回答で申し訳ありません。もし私が最初にこれらすべてを理解していたら、その方法を提示するためにより適切なフォーラムを選んだでしょう。

于 2011-12-29T18:19:48.550 に答える
0

RowState を modified に変更する必要がある場合は、DataRow.SetModified()メソッドを呼び出します。

于 2011-12-28T23:37:09.967 に答える