使用INotifyDataErrorInfo时出现多个验证错误,旧消息不会消失

时间:2016-03-17 08:23:04

标签: c# wpf validation

在WPF项目中,我正在使用INotifyDataErrorInfo实现验证TextBox输入。可能会发生多个错误。

当我输入导致多个错误的内容时,会显示所有验证错误。但是,当我修复一个错误时,验证消息不会更改,这意味着会显示错误的错误消息。只有当我修复所有错误时,消息才会消失。

这是我的实现的问题,还是WPF实现只在HasErrors改变时才重新获取验证消息?但是,使用调试器逐步调试它,我可以看到GetErrors和HasErrors都被调用。

使用附加示例重现的步骤:

  • 输入333333.显示2条验证消息。
  • 将前导3更改为2.尽管第一个错误已得到修复,但仍会显示两条验证消息。
  • 将第二个3更改为0.两个消息都消失
  • 再次将第二个数字更改为3。显示第二条验证消息。
  • 再次将前导数字更改为3。仅显示第二条验证消息,但它应显示两者。

是的,这个例子没有多大意义,因为我实际上可以摆脱第一次检查,因为它包含在第二次检查中。

观点:

<Window x:Class="WeirdValidationTest.ValidationTestView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WeirdValidationTest"
        Height="60" Width="400">
    <Window.DataContext>
        <local:ValidationTestViewModel />
    </Window.DataContext>
    <Window.Resources>
        <local:ValidationErrorsToStringConverter x:Key="ValErrToString" />
        <ControlTemplate x:Key="ErrorTemplate">
            <Border BorderBrush="Red" BorderThickness="1">
                <StackPanel Orientation="Horizontal">
                    <AdornedElementPlaceholder />
                    <TextBlock Text="{Binding Converter={StaticResource ValErrToString}}" Background="White" />
                </StackPanel>
            </Border>
        </ControlTemplate>
    </Window.Resources>
    <Grid>
        <TextBox MaxLength="6" Validation.ErrorTemplate="{StaticResource ErrorTemplate}"
                 Text="{Binding InputValue, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left"
                 VerticalAlignment="Top" Width="100" />
    </Grid>
</Window>

(代码隐藏是构造函数中简单的InitializeComponent调用)

ViewModel:

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace WeirdValidationTest
{
    internal class ValidationTestViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
    {
        private readonly Dictionary<string, List<string>> errors = new Dictionary<string, List<string>>();
        private uint inputValue;

        public event PropertyChangedEventHandler PropertyChanged;

        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        public uint InputValue
        {
            get
            {
                return inputValue;
            }
            set
            {
                if (inputValue != value)
                {
                    if (value / 100000 == 2)
                    {
                        RemoveError("InputValue",
                                    String.Format("Must be in range {0}...{1}", "200000", "299999"));
                    }
                    else
                    {
                        AddError("InputValue",
                                 String.Format("Must be in range {0}...{1}", "200000", "299999"));
                    }

                    uint testNumber = (uint) ((value) / 1e4);

                    {
                        string msg = string.Format("Must start with value {0}", "20....");
                        if (testNumber != 20)
                        {
                            AddError("InputValue", msg);
                        }
                        else
                        {
                            RemoveError("InputValue", msg);
                        }
                    }

                    inputValue = value;
                    OnPropertyChanged();
                }
            }
        }

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

        public IEnumerable GetErrors(string propertyName)
        {
            List<string> val;
            errors.TryGetValue(propertyName, out val);
            return val;
        }

        void AddError(string propertyName, string messageText)
        {
            List<string> errList;
            if (errors.TryGetValue(propertyName, out errList))
            {
                if (!errList.Contains(messageText))
                {
                    errList.Add(messageText);
                }
            }
            else
            {
                errList = new List<string> { messageText };
                errors.Add(propertyName, errList);
            }
            OnErrorsChanged(propertyName);
        }

        void RemoveError(string propertyName, string messageText)
        {
            List<string> errList;
            if (errors.TryGetValue(propertyName, out errList))
            {
                errList.Remove(messageText);
                if (errList.Count == 0)
                {
                    errors.Remove(propertyName);
                }
            }
            OnErrorsChanged(propertyName);
        }

        private void OnErrorsChanged(string propertyName)
        {
            var handler = ErrorsChanged;
            if (handler != null)
            {
                handler(this, new DataErrorsChangedEventArgs(propertyName));
            }
        }

        private void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

转换器:

using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WeirdValidationTest
{
    [ValueConversion(typeof(ReadOnlyObservableCollection<ValidationError>), typeof(string))]
    internal class ValidationErrorsToStringConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
           var errorCollection = value as ReadOnlyObservableCollection<ValidationError>;

            if (errorCollection == null)
            {
                return DependencyProperty.UnsetValue;
            }

            return String.Join(", ", errorCollection.Select(e => e.ErrorContent.ToString()));
        }

        public object ConvertBack(object value, Type targetType, object parameter,
                                  CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

目标.net版本为4.5

编辑: 遇到与IDataErrorInfo类似的问题,请看这个问题: Validation Rule not updating correctly with 2 validation rules 更换转换器有帮助

2 个答案:

答案 0 :(得分:1)

很好的解释,你的代码看起来也很不错。我以我的方式解决了你的问题希望你喜欢它。 只需更改像这样的AddError和RemoveError方法,

void AddError(string propertyName, string messageText)
        {
            List<string> errList;
            if (errors.TryGetValue(propertyName, out errList))
            {
                if (!errList.Contains(messageText))
                {
                    errList.Add(messageText);
                    errors.Remove(propertyName);
                    OnErrorsChanged(propertyName);
                    if (errList != null)
                        errors.Add(propertyName, errList);
                }
            }
            else
            {
                errList = new List<string> { messageText };
                errors.Add(propertyName, errList);
                OnErrorsChanged(propertyName);
            }
        }
        void RemoveError(string propertyName, string messageText)
        {
            List<string> errList;
            if (errors.TryGetValue(propertyName, out errList))
            {
                errList.Remove(messageText);
                errors.Remove(propertyName);
            }
            OnErrorsChanged(propertyName);
            if (errList != null)
                errors.Add(propertyName, errList);

        }

答案 1 :(得分:0)

回答我自己的问题,因为在所有这些时间之后,我找到了罪魁祸首。

主要问题是我的ErrorTemplate。 Validation.Errors绑定到ErrorTemplate中的TextBox。另请参见Binding ObservableCollection<> to a TextBox。 由于Validation.ErrorsObservableCollecton,因此不会触发PropertyChanged事件。这样,绑定(错误模板)就不会更新。