如何在WPF中获取DataGrid控件的用户输入错误并点击按钮触发提示
问题原因
- DataGrid默认开启UI虚拟化,仅可视区域内的单元格会被渲染到视觉树中,非可视区域的单元格无法通过遍历VisualChildren获取,导致漏检错误。
- 点击按钮时如果单元格仍处于编辑模式,绑定未完成更新,校验错误还未挂载到对应视觉元素上。
- 校验错误的附加属性通常挂载在单元格内部的
TextBox元素上,而非DataGridCell本身,直接读取DataGridCell的HasError属性会返回false。
推荐解决方案:数据源层面校验(MVVM友好,无漏检)
你已经实现了INotifyDataErrorInfo接口,直接遍历数据源集合校验即可,完全不依赖视觉树,性能和准确率都远高于遍历视觉元素。
- 首先补充
Person类的INotifyDataErrorInfo实现示例:
using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; namespace Get_User_input_Errors_From_DataGrid { public class Person : INotifyPropertyChanged, INotifyDataErrorInfo { private string _name; private int _age; private int _weight; private readonly Dictionary<string, List<string>> _errors = new(); public string Name { get => _name; set { _name = value; OnPropertyChanged(nameof(Name)); ValidateName(); } } public int Age { get => _age; set { _age = value; OnPropertyChanged(nameof(Age)); ValidateAge(); } } public int Weight { get => _weight; set { _weight = value; OnPropertyChanged(nameof(Weight)); ValidateWeight(); } } public bool HasErrors => _errors.Any(); public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; public event PropertyChangedEventHandler PropertyChanged; private void ValidateName() { if (string.IsNullOrWhiteSpace(Name)) AddError(nameof(Name), "姓名不能为空"); else RemoveError(nameof(Name)); } private void ValidateAge() { if (Age < 0 || Age > 150) AddError(nameof(Age), "年龄需在0-150之间"); else RemoveError(nameof(Age)); } private void ValidateWeight() { if (Weight < 0 || Weight > 300) AddError(nameof(Weight), "体重需在0-300之间"); else RemoveError(nameof(Weight)); } private void AddError(string propertyName, string error) { if (!_errors.ContainsKey(propertyName)) _errors[propertyName] = new List<string>(); if (!_errors[propertyName].Contains(error)) { _errors[propertyName].Add(error); ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); } } private void RemoveError(string propertyName) { if (_errors.Remove(propertyName)) ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); } public IEnumerable GetErrors(string propertyName) { return string.IsNullOrEmpty(propertyName) || !_errors.ContainsKey(propertyName) ? Enumerable.Empty<string>() : _errors[propertyName]; } protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
- 修改按钮点击逻辑,直接遍历数据源收集错误:
private void Button_Click(object sender, RoutedEventArgs e) { var vm = DataContext as ViewModel; var errorList = new List<string>(); for (int i = 0; i < vm.People.Count; i++) { var person = vm.People[i]; if (person.HasErrors) { var ageErrors = person.GetErrors(nameof(Person.Age))?.Cast<string>().ToList(); if (ageErrors != null && ageErrors.Any()) errorList.Add($"第{i+1}行年龄错误:{string.Join(",", ageErrors)}"); var nameErrors = person.GetErrors(nameof(Person.Name))?.Cast<string>().ToList(); if (nameErrors != null && nameErrors.Any()) errorList.Add($"第{i+1}行姓名错误:{string.Join(",", nameErrors)}"); var weightErrors = person.GetErrors(nameof(Person.Weight))?.Cast<string>().ToList(); if (weightErrors != null && weightErrors.Any()) errorList.Add($"第{i+1}行体重错误:{string.Join(",", weightErrors)}"); } } if (errorList.Any()) { MessageBox.Show($"检测到以下错误:\n{string.Join("\n", errorList)}"); } else { MessageBox.Show("数据校验通过"); } }
可选方案:修复视觉树遍历逻辑
如果确实需要从视觉层获取错误,修改如下:
- 先给DataGrid命名,方便调用:
<DataGrid x:Name="MyDataGrid" AutoGenerateColumns="False" ItemsSource="{Binding Path=People,Mode=TwoWay}">
- 修改按钮点击逻辑,先提交编辑,再校验单元格内部元素:
private void Button_Click(object sender, RoutedEventArgs e) { // 先提交所有未保存的编辑 MyDataGrid.CommitEdit(DataGridEditingUnit.Cell, true); MyDataGrid.CommitEdit(DataGridEditingUnit.Row, true); var errorList = new List<string>(); var allCells = Helpers.FindVisualChildren<DataGridCell>(MyDataGrid).ToList(); foreach (var cell in allCells) { // 检查单元格自身和内部所有子元素的校验错误 var elementsWithError = Helpers.FindVisualChildren<DependencyObject>(cell) .Where(Validation.GetHasError).ToList(); if (elementsWithError.Any()) { var cellContent = cell.Content is TextBlock tb ? tb.Text : cell.Content.ToString(); errorList.Add($"单元格错误:{cellContent}"); } } if (errorList.Any()) { MessageBox.Show($"检测到以下错误:\n{string.Join("\n", errorList)}"); } }
注意:该方案仍受UI虚拟化影响,非可视区域的单元格错误无法检测,仅适合小数据量场景使用。
内容的提问来源于stack exchange,提问作者Piotr Golacki
相关产品推荐
相关产品推荐

