You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 13:32:50