WPF集合项重复值验证问题:如何标记全部重复项
解决WPF集合重复项全标记错误的问题
问题原因
你原来的CollectionUniqueValueValidationRule是在集合绑定层面做验证,一旦发现重复就返回单个错误,WPF只会将这个错误关联到集合的绑定对象上,无法映射到每个重复的数据项,所以只能标记一个项。
解决方案思路
要实现所有重复项都标记错误,需要在单个数据项的属性级别执行验证,让每个项能感知整个集合的状态,判断自身值是否重复。推荐使用INotifyDataErrorInfo接口(比IDataErrorInfo更灵活,支持多错误和异步验证)。
步骤1:改造数据模型
让SomeData实现INotifyPropertyChanged和INotifyDataErrorInfo,持有父集合的引用,并在属性变更或集合变化时执行重复验证:
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; public class SomeData : INotifyPropertyChanged, INotifyDataErrorInfo { private string _targetProperty; private readonly Dictionary<string, List<string>> _errorStore = new Dictionary<string, List<string>>(); // 引用父集合,用于判断重复 public ObservableCollection<SomeData> ParentCollection { get; set; } public string TargetProperty { get => _targetProperty; set { _targetProperty = value; OnPropertyChanged(); ValidateTargetProperty(); } } // 验证当前属性值是否在集合中重复 private void ValidateTargetProperty() { var errors = new List<string>(); if (ParentCollection != null) { var duplicateCount = ParentCollection.Count(item => string.Equals(item.TargetProperty, TargetProperty, StringComparison.Ordinal)); if (duplicateCount > 1) { errors.Add("该值已存在"); } } UpdateErrorState(nameof(TargetProperty), errors); } // 更新错误状态并触发通知 private void UpdateErrorState(string propertyName, List<string> errors) { if (errors.Any()) { if (_errorStore.ContainsKey(propertyName)) _errorStore[propertyName] = errors; else _errorStore.Add(propertyName, errors); ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); } else if (_errorStore.ContainsKey(propertyName)) { _errorStore.Remove(propertyName); ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); } } // 集合变化时调用,重新验证当前项 public void ReValidate() { ValidateTargetProperty(); } #region 接口实现 public event PropertyChangedEventHandler PropertyChanged; public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; public bool HasErrors => _errorStore.Any(); public IEnumerable GetErrors(string propertyName) { if (string.IsNullOrEmpty(propertyName) || !_errorStore.ContainsKey(propertyName)) return Enumerable.Empty<string>(); return _errorStore[propertyName]; } protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } #endregion }
步骤2:处理集合变化
在ViewModel中监听集合的CollectionChanged事件,当添加/删除项时,为每个项设置父集合引用并触发重新验证:
using System.Collections.ObjectModel; using System.ComponentModel; public class MainViewModel { public ObservableCollection<SomeData> Datas { get; } public MainViewModel() { Datas = new ObservableCollection<SomeData>(); Datas.CollectionChanged += OnDatasCollectionChanged; } private void OnDatasCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { // 为所有项设置父集合引用并重新验证 foreach (var item in Datas) { item.ParentCollection = Datas; item.ReValidate(); } } }
步骤3:XAML绑定与错误模板
在ItemsControl的ItemTemplate中,为输入控件开启ValidatesOnNotifyDataErrors=True,并定义错误模板:
<ItemsControl ItemsSource="{Binding Datas}"> <ItemsControl.ItemTemplate> <DataTemplate> <TextBox Margin="4" Text="{Binding TargetProperty, ValidatesOnNotifyDataErrors=True, UpdateSourceTrigger=PropertyChanged}"> <!-- 自定义错误显示模板 --> <Validation.ErrorTemplate> <ControlTemplate> <DockPanel> <TextBlock Foreground="Red" Text="{Binding [0].ErrorContent}" DockPanel.Dock="Right" Margin="4 0"/> <Border BorderBrush="Red" BorderThickness="1"> <AdornedElementPlaceholder/> </Border> </DockPanel> </ControlTemplate> </Validation.ErrorTemplate> </TextBox> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl>
效果说明
- 当输入重复值时,所有值相同的项都会立即显示错误提示
- 添加/删除项时,所有项会自动重新验证,错误状态实时更新
内容的提问来源于stack exchange,提问作者limeniye
相关产品推荐
相关产品推荐

