INotifyDataErrorInfo不会消失红色边框

时间:2014-10-28 07:32:07

标签: c# .net validation xaml

我尝试实现INotifyDataErrorInfo,但在尝试验证ObservableCollection属性时没有成功。

问题是,如果收集错误,我会看到红色边框,但如果我更正了收藏品,红色边框就不会再消失了。

有人知道如何解决这个问题吗?

我设置了一个小样本来演示问题:

<Window x:Class="Validation.ValidationWindow3"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="ValidationWindow3" Height="300" Width="300">
<DockPanel LastChildFill="True">
    <TextBlock DockPanel.Dock="Top">Click button Add two times.<LineBreak/>
        => Red border should appear.<LineBreak/>
        <LineBreak/>
        Select second line in listbox then click button Remove.<LineBreak/>
        =>Red border should disappear.</TextBlock>
    <Button DockPanel.Dock="Bottom" Click="OnOk">Ok</Button>
    <Button DockPanel.Dock="Top" Click="OnAdd">Add</Button>
    <Button DockPanel.Dock="Top" Click="OnRemove">Remove</Button>
    <ListBox ItemsSource="{Binding ListOfNumbers, NotifyOnValidationError=True}" SelectedItem="{Binding SelectedNumber}" />
</DockPanel>

代码背后:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace Validation
{
/// <summary>
/// Interaction logic for ValidationWindow2.xaml
/// </summary>
public partial class ValidationWindow3 : Window, INotifyDataErrorInfo
{
    public ObservableCollection<int> ListOfNumbers
    {
        get { return (ObservableCollection<int>)GetValue(ListOfNumbersProperty); }
        set { SetValue(ListOfNumbersProperty, value); }
    }
    public static readonly DependencyProperty ListOfNumbersProperty =
        DependencyProperty.Register("ListOfNumbers", typeof(ObservableCollection<int>), typeof(ValidationWindow3), new PropertyMetadata(null, OnPropertyChanged));



    public int SelectedNumber
    {
        get { return (int)GetValue(SelectedNumberProperty); }
        set { SetValue(SelectedNumberProperty, value); }
    }
    public static readonly DependencyProperty SelectedNumberProperty =
        DependencyProperty.Register("SelectedNumber", typeof(int), typeof(ValidationWindow3), new PropertyMetadata(-1));



    public ValidationWindow3()
    {
        InitializeComponent();
        ListOfNumbers = new ObservableCollection<int>();
        DataContext = this;
    }

    private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ValidationWindow3 instance = d as ValidationWindow3;
        ObservableCollection<int> coll = (ObservableCollection<int>)e.NewValue;
        coll.CollectionChanged += instance.coll_CollectionChanged;
    }

    void coll_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        CheckProperty("ListOfNumbers");
    }



    private void OnOk(object sender, RoutedEventArgs e)
    {
        if(HasErrors)
        {
            IEnumerable list = GetErrors(null);
            string msg = "";
            foreach(var item in list)
            {
                msg += item.ToString();
            }
            MessageBox.Show(msg);
            return;
        }
        DialogResult = true;
    }


    void CheckProperty([CallerMemberName] string propertyName = "")
    {
        bool isValid = true;
        string msg = null;

        switch(propertyName)
        {
        case "ListOfNumbers":
            msg = "Only even numbers allowed!";
            foreach(int item in ListOfNumbers)
            {
                if(item % 2 > 0)
                {
                    isValid = false;
                }
            }
            break;
        default:
            break;
        }
        if(!isValid)
        {
            AddError(propertyName, msg);
        }
        else if(msg != null)
        {
            RemoveError(propertyName, msg);
        }
    }

    // Adds the specified error to the errors collection if it is not 
    // already present, inserting it in the first position if isWarning is 
    // false. Raises the ErrorsChanged event if the collection changes. 
    public void AddError(string propertyName, string error, bool isWarning=false)
    {
        if(!errors.ContainsKey(propertyName))
            errors[propertyName] = new List<string>();

        if(!errors[propertyName].Contains(error))
        {
            if(isWarning)
                errors[propertyName].Add(error);
            else
                errors[propertyName].Insert(0, error);
            RaiseErrorsChanged(propertyName);
        }
    }

    // Removes the specified error from the errors collection if it is
    // present. Raises the ErrorsChanged event if the collection changes.
    public void RemoveError(string propertyName, string error)
    {
        if(errors.ContainsKey(propertyName) &&
            errors[propertyName].Contains(error))
        {
            errors[propertyName].Remove(error);
            if(errors[propertyName].Count == 0)
                errors.Remove(propertyName);
            RaiseErrorsChanged(propertyName);
        }
    }

    public void RaiseErrorsChanged(string propertyName)
    {
        if(ErrorsChanged != null)
            ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
    }




    #region INotifyDataErrorInfo Members

    private Dictionary<String, List<String>> errors =
        new Dictionary<string, List<string>>();

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public System.Collections.IEnumerable GetErrors(string propertyName)
    {
        if (errors.Count < 1)
        {
            return null;
        }
        if(String.IsNullOrEmpty(propertyName))
        {
            return errors.SelectMany(err => err.Value.ToList());
        }
        if(!errors.ContainsKey(propertyName))
            return null;
        return errors[propertyName];
    }

    public bool HasErrors
    {
        get { return errors.Count > 0; }
    }

    #endregion

    static int _nextNumber = 0;

    private void OnAdd(object sender, RoutedEventArgs e)
    {
        ListOfNumbers.Add(_nextNumber++);
    }

    private void OnRemove(object sender, RoutedEventArgs e)
    {
        ListOfNumbers.Remove(SelectedNumber);
    }
}
}

编辑:

我发现验证本身没有任何问题。这似乎是与ListBox相关的问题。如果我将另一个TextBox绑定到ListOfNumbers,我可以看到此TextBox上的边框工作正常。

这是我添加的内容:

        <TextBox DockPanel.Dock="Top" Text="{Binding ListOfNumbers, NotifyOnValidationError=True}" />

那么为什么ListBox上的红色边框错了?

2 个答案:

答案 0 :(得分:1)

我想我找到了原因:

ListBox正在验证SelectedItem。因此,如果我删除此项,则绑定到SelectedItem的变量SelectedNumber具有不再在集合中的值。这给了我一个红色的盒子。

我不认为这是ListBox的正确行为,但如果我记住这一点,我可以根据情况使用一些变通办法:

  1. 删除SelectedItem后,将SelectedItem设置为集合中的其他项目,或设置为-1。
  2. 使用SelectedIndex而不是SelectedItem,因为这没有这样的问题。
  3. 使用Mode = OneWay进行SelectedItem绑定

答案 1 :(得分:0)

我也遇到了这个问题。我通过删除 Validation.ErrorTemplate 解决了这个问题:

<Style>
    <Setter Property="Validation.ErrorTemplate">
        <Setter.Value>
            <ControlTemplate>
                <AdornedElementPlaceholder/>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

并在绑定元素的DataTemplate中添加错误指示,如下:

 <TextBlock Text="!" FontWeight="Bold" Foreground="Red" Margin="5" Visibility="{Binding HasErrors, Converter={StaticResource BoolToVisibilityConverter}}"/>