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

问题描述 投票:0回答:3

在 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 调用)

视图模型:

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
,请参阅这个问题: 验证规则未使用 2 个验证规则正确更新 更换转换器有帮助

c# wpf validation
3个回答
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);

        }

0
投票

回答我自己的问题,因为毕竟这一次我找到了罪魁祸首。

主要问题是我的ErrorTemplate。

Validation.Errors
绑定到 ErrorTemplate 中的文本框。另请参阅将 ObservableCollection<> 绑定到 TextBox。 由于
Validation.Errors
ObservableCollecton
,因此它不会触发
PropertyChanged
事件。这样,绑定(错误模板)不会更新。


0
投票

我已使用已识别的错误消息尝试了以下方法。两个(或多个)属性中的验证方法将相同的 ID 应用于两个属性中的验证。如果任一验证成功,则无论属性如何,都会通过 ID 删除错误。

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfValidation
{
    internal class DataClass :  INotifyPropertyChanged, INotifyDataErrorInfo

    {
        private readonly Dictionary<string, List<Error>> _errorsByPropertyName = new Dictionary<string, List<Error>>();

        int _Field1;
        public int Field1
        {
            get => _Field1;
            set
            {
                if (_Field1 != value)
                {
                    _Field1 = value;
                    ValidateField1();
                    NotifyPropertyChanged(nameof(Field1));
                    NotifyPropertyChanged(nameof(Field2));
                }
            }
        }

        internal void ValidateField1()
        {
            ClearErrors(nameof(Field1));
            if (Field1 > 5)
            {
                AddError(nameof(Field1), new Error("Field1_ID1","Field1 > 5"));
                return;
            }
            if (Field2 > Field1)
            {
                AddError(nameof(Field1), new Error("Range", "V1A Field2 > Field"));
                AddError(nameof(Field2), new Error("Range", "V1B Field2 > Field"));
            }
            else RemoveError(nameof(Field2), "Range");

        }
        int _Field2;
        public int Field2
        {
            get => _Field2;
            set
            {
                if (_Field2 != value)
                {
                    _Field2 = value;
                    ValidateField2();
                    NotifyPropertyChanged(nameof(Field2));
                    NotifyPropertyChanged(nameof(Field1));
                }
            }
        }

        internal void ValidateField2()
        {
            ClearErrors(nameof(Field2));
            if (Field2 > Field1)
            {
                AddError(nameof(Field1), new Error("Range", "V2A Field2 > Field"));
                AddError(nameof(Field2), new Error("Range", "V2B Field2 > Field"));
            }
            else
                RemoveError(nameof(Field1), "Range");
        }

        public void RemoveError(string propertyName, string ErrorId)
        {
            if (_errorsByPropertyName.ContainsKey(propertyName))
            {
                var errorList = _errorsByPropertyName[propertyName];
                for (int i=errorList.Count -1; i>=0; i--)
                {
                    if (errorList[i].ID == ErrorId)
                        errorList.RemoveAt(i);
                }
            }
        }
        public bool HasErrors => _errorsByPropertyName.Any();

        public event PropertyChangedEventHandler PropertyChanged;
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        private void AddError(string propertyName, Error error)
        {
            if (!_errorsByPropertyName.ContainsKey(propertyName))
                _errorsByPropertyName[propertyName] = new List<Error>();

            if (!_errorsByPropertyName[propertyName].Contains(error))
            {
                _errorsByPropertyName[propertyName].Add(error);
                OnErrorsChanged(propertyName);
            }
        }
        private void OnErrorsChanged(string propertyName)
        {
            ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
        }

        private void ClearErrors(string propertyName)
        {
            if (_errorsByPropertyName.ContainsKey(propertyName))
            {
                _errorsByPropertyName.Remove(propertyName);
                OnErrorsChanged(propertyName);
            }
        }

        public IEnumerable GetErrors(string propertyName)
        {
            return _errorsByPropertyName.ContainsKey(propertyName) ?
            _errorsByPropertyName[propertyName] : null;
        }

        #region "INotifyPropertyChanged Members"


        public void NotifyPropertyChanged(string info)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
        }
        #endregion

        public class Error
        {
            public string ID;
            public string ErrorMessage;

            public Error(string iD, string errorMessage)
            {
                ID = iD;
                ErrorMessage = errorMessage;
            }

            public override string ToString() { return ErrorMessage; }
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.