在 WPF 项目中,我使用 INotifyDataErrorInfo 实现验证 TextBox 输入。可能会出现多个错误。
当我输入导致多个错误的内容时,会显示所有验证错误。但是,当我修复一个错误时,验证消息不会更改,这意味着会显示错误的错误消息。只有当我修复所有错误时,该消息才会消失。
这是我的实现的问题吗?还是 WPF 实现仅在 HasErrors 更改时重新获取验证消息?然而,使用调试器单步调试它,我可以看到 GetErrors 和 HasErrors 都被调用了。
使用附加示例重现的步骤:
是的,这个例子没有多大意义,因为我实际上可以摆脱第一个检查,因为它包含在第二个检查中。
景色:
<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 个验证规则正确更新
更换转换器有帮助
很好的解释,你的代码看起来也不错。我用我的方式解决了你的问题希望你喜欢。 只需像这样更改您的 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);
}
回答我自己的问题,因为毕竟这一次我找到了罪魁祸首。
主要问题是我的ErrorTemplate。
Validation.Errors
绑定到 ErrorTemplate 中的文本框。另请参阅将 ObservableCollection<> 绑定到 TextBox。
由于 Validation.Errors
是 ObservableCollecton
,因此它不会触发 PropertyChanged
事件。这样,绑定(错误模板)不会更新。
我已使用已识别的错误消息尝试了以下方法。两个(或多个)属性中的验证方法将相同的 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; }
}
}
}