1

我有一个绑定到 DataTable 的 WPF DataGrid。我从数据库中的任意表填充基础数据集。我已附加到 DataTableRowChangingRowChanged事件。当用户更改一行时,这些事件会触发并允许我验证该行。

为了从 DataGrid 获得最佳行为,对我来说,显然e.Row.RowError应该设置消息并从RowChanging事件处理程序中抛出异常。我有一些 xaml 将行标记为行标题中的错误,因此它是可见的,并且我得到了一个带有错误消息的漂亮工具提示。最优,我的意思是当按照描述处理验证时,人们期望使用这些网格的转义序列按预期工作。尝试从RowChanged事件中执行相同的验证会导致一些无法正确回滚编辑的时髦行为。

我遇到的问题是我需要更新底层数据集,以便应用所有数据库验证规则,并且可以在RowChanging处理程序中检测到与其他用户更改的冲突。如果操作失败,我可以按照描述标记验证。但是,e.Row.RowState它以 Unchanged 的​​形式出现,如果我将其包含的 DataSet 传递给我的 DB 更新方法,它的DataAdapter.Update(myDataTable)方法不会看到该行已更改,因此什么也不做。这种行为与我在RowChanged处理程序中执行相同操作时会发生的情况形成鲜明对比。此时,记录(当前/原始/建议)值被更新,记录被标记为已修改。

此时的 DataAdapter 更新会导致数据库活动。但是,在失败的情况下,我在事件序列中的错误点。我可以标记错误,但网格的回滚行为将无法正常工作(通常会导致更改的单元格不回滚)。

我的问题是,如何获得处于修改状态的记录(或记录的副本??)以便更新数据库?我通常使用类型化的 DataSet,但这次我要处理任意表,因此使用的是 DataSet。

4

2 回答 2

1

好的,它有点有趣,但我终于解决了。关键是处理处理程序中的添加和修改事件以及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 中使用的验证规则和代码隐藏以获得可靠的错误指示。抱歉这么长的回答。如果我一开始就弄清楚了这一切,我会选择一个更合适的论坛来介绍这种方法。

于 2011-12-29T18:19:48.550 回答
0

如果您只需要将 RowState 更改为已修改,您将调用该DataRow.SetModified()方法。

于 2011-12-28T23:37:09.967 回答