【问题标题】:WPF DataGrid - How to cancel edit row after validation error?WPF DataGrid - 验证错误后如何取消编辑行?
【发布时间】:2019-07-29 10:14:24
【问题描述】:

我想在用户单击✖按钮时取消连续更改。

source code

private void CancelChangesButton_Click(object sender, RoutedEventArgs e)
{
    datagrid.CancelEdit();
}

CancelEdit() 效果很好,直到...我的 DateConverter 无法 ConvertBack 字符串。 当 ViewModel 的属性设置器抛出异常时,也会发生相同的行为。 我不能在 DataGrid 中做任何事情。唯一的方法是当光标在红色单元格时按 ESC 键。

我尝试其他的东西:

datagrid.CancelEdit(DataGridEditingUnit.Row);
datagrid.CancelEdit(DataGridEditingUnit.Cell);
datagrid.CommitEdit();
datagrid.IsReadOnly = true;
// Add new item

什么都没发生。

所以我开始挖掘 .NET Framework 源代码,发现了这个:

public class DataGrid : MultiSelector
...
    public bool CancelEdit(DataGridEditingUnit editingUnit)
    {
        return EndEdit(CancelEditCommand, CurrentCellContainer, editingUnit, true);
    }

-> .NET Reference source

这里最重要的是 CurrentCellContainer,它从 CurrentCell 中获取值。接下来,我发现 CurrentCell 正在关注焦点。当我点击✖按钮时,CurrentCell 变为 Action 列中的单元格,当我点击 DataGrid 外部时,CurrentCell 变为 null。

因此,我必须将 CurrentCell 更改为出现验证错误的单元格,然后调用 CancelEdit()。 我觉得对吗?

如何找到所有出现验证错误的单元格?

还有其他方法可以取消编辑吗?

【问题讨论】:

    标签: c# wpf datagrid


    【解决方案1】:

    红色单元格显示验证错误。只要存在验证错误(用户按下 Escape 键除外),您就无法取消编辑模式。

    唯一的解决方案是通过简单地还原输入来手动解决错误。

    算法如下:

    1. 获取当前单元格
    2. 获取当前单元格的容器
    3. 检查容器(单元)是否存在验证错误。如果继续第 4 步,否则只需取消编辑(跳至第 9 步)
    4. 获取单元格模板的编辑TextBox
    5. 标识编辑TextBox.Text属性的绑定源(数据项)属性
    6. 获取属性的值(对通用行为使用反射)
    7. 还原内容
    8. 将键盘焦点移回编辑TextBox,这将触发重新验证并定义取消的目标单元格。
    9. 取消编辑

    实施:

    private void CancelChangesButton_Click(object sender, RoutedEventArgs e)
    {
      DependencyObject cellItemContainer = this.datagrid.ItemContainerGenerator.ContainerFromItem(
        (this.datagrid.CurrentCell.Item as SomethingItem));
    
      // If the current cell has validation errors find the edit TextBox child control
      if (Validation.GetHasError(cellItemContainer) && TryFindChildElement(cellItemContainer, out TextBox editTextBox))
      {
        // Get the property name of he binding source
        var propertyName = (editTextBox.BindingGroup.BindingExpressions.FirstOrDefault() as BindingExpression)?.ResolvedSourcePropertyName ?? string.Empty;
    
        // Use reflection to get the value of the binding source
        object value = this.datagrid.CurrentCell.Item.GetType().GetProperty(propertyName).GetValue(this.datagrid.CurrentCell.Item);
    
        // Check which ToString() to invoke
        editTextBox.Text = value is DateTime date 
          ? date.ToShortDateString() 
          : value.ToString();
    
        // Trigger validation and define which cell to cancel the edit
        // This is required because the edit TexBox lost focus
        Keyboard.Focus(editTextBox);
      }
    
      this.datagrid.CancelEdit();
    }
    
    // Traverses the visual tree to find a child element of type TElement
    private bool TryFindVisualChild<TChild>(DependencyObject parent, out TChild resultElement) where TChild : DependencyObject
    {
      resultElement = null;
      for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
      {
        DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);
    
        if (childElement is Popup popup)
        {
          childElement = popup.Child;
        }
    
        if (childElement is TChild)
        {
          resultElement = childElement as TChild;
          return true;
        }
    
        if (TryFindVisualChild(childElement, out resultElement))
        {
          return true;
        }
      }
    
      return false;
    }
    

    【讨论】:

    • 您的解决方案有效,但我必须单击两次(或多次)才能取消编辑行...您不需要使用反射,当您有 BindingGroup 时,您可以调用editTextBox.BindingGroup.CancelEdit();。 .. 我正在寻找一种适用于不同类型单元格的解决方案,而不仅仅是文本框。但是,我们正朝着正确的方向前进:)
    • 好的。有趣的。这个对我有用。只需单击按钮即可取消。我正在使用反射从绑定源属性中获取值。稍后我会考虑一个更通用的解决方案。但是还有哪些其他输入控件使用验证?关键是要解决阻止编辑被取消的验证错误。在所有其他情况下,使用 DataGrid.CancelEdit() 的原始方法都有效
    • 最后,我意识到我不需要找出哪个单元格有验证错误。 +1 为您提供帮助。我测试了cellItemContainer.BindingGroup.CancelEdit() 并且它可以工作,但是在出错之后,它并没有退出编辑模式。
    【解决方案2】:

    我意识到我不需要找到存在验证错误的单元格。我需要做的就是在所有单元格上调用 CancelEdit()。

    private void CancelChangesButton_Click(object sender, RoutedEventArgs e)
    {
        var cc = dataGrid.CurrentCell;
        foreach (var col in datagrid.Columns)
        {
            datagrid.CurrentCell = new DataGridCellInfo(datagrid.CurrentItem, col);
            datagrid.CancelEdit();
        }
        dataGrid.CurrentCell = cc;
    }
    

    它也适用于 DataGridTemplateColumn。 solution code


    但是,如果您想找出哪些单元格包含验证错误,则需要深入研究。感谢@BionicCode,我找到了解决方案。

    可以得到可视化的DataGridRow:

    DataGridRow row = (DataGridRow)datagrid.ItemContainerGenerator.ContainerFromItem(item);
    

    然后你可以检查错误:

    if (Validation.GetHasError(row))
    

    您还可以访问row.BindingGroup,其中包含此行中的所有绑定 (.BindingExpressions) 以及许多其他信息(IsDirty、ValidationErrors、ValidationRules、CancelEdit())

    但是,当您想要检查单元格中的错误时,就没有那么容易了。不幸的是,DataGridCell 不包含有关错误的信息,Validation.GetHasError(cell) 不起作用。您需要深入了解可视化树。

    private void CancelChangesCellsHavingError()
    {
        SomethingItem item = datagrid.CurrentItem as SomethingItem;
    
        DataGridRow row = (DataGridRow)datagrid.ItemContainerGenerator.ContainerFromItem(item);
    
        if (Validation.GetHasError(row))
        {
            var cc = dataGrid.CurrentCell;
            foreach (DataGridColumn col in datagrid.Columns)
            {
                DataGridCell cell = (DataGridCell)col.GetCellContent(item).Parent;
                List<DependencyObject> errs = GetVisualChildrenHavingError(cell);
                if (errs != null)
                {
                    datagrid.CurrentCell = new DataGridCellInfo(item, col);
                    datagrid.CancelEdit(DataGridEditingUnit.Cell);
                }
            }
            dataGrid.CurrentCell = cc;
        }
    }
    
    /// <summary>
    /// Returns all visual children that HasError. Return null if nothing is found.
    /// </summary>
    public static List<DependencyObject> GetVisualChildrenHavingError(DependencyObject parent)
    {
        List<DependencyObject> result = null;
        GetVisualChildrenHavingError(parent, ref result);
        return result;
    }
    
    private static void GetVisualChildrenHavingError(DependencyObject parent, ref List<DependencyObject> result)
    {
        for (int childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
        {
            DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);
    
            if (Validation.GetHasError(childElement))
            {
                if (result == null)
                    result = new List<DependencyObject>();
    
                result.Add(childElement);
            }
    
            GetVisualChildrenHavingError(childElement, ref result);
        }
    }
    

    有用的链接——绑定、验证、DataGrid:

    【讨论】:

      猜你喜欢
      • 2015-10-04
      • 1970-01-01
      • 2011-06-29
      • 1970-01-01
      • 2013-12-25
      • 2011-11-10
      • 1970-01-01
      • 2012-12-09
      • 2011-07-03
      相关资源
      最近更新 更多