INotifyDataErrorInfo(有时)不起作用
INotifyDataErrorInfo (sometimes) does not work
我写了一个控件,派生自文本框,我可以在其中输入特殊格式的数字。
为确保此格式正确,我还实施了 INotifyDataErrorInfo 以进行验证。
然而,经过几次测试后,一切似乎都很好。错误修复后,验证会弹出并再次消失。
但是现在,我想在另一个 window 中使用相同的控件,但它不再起作用了。验证发生,错误被添加到字典中并调用 OnErrorsChanged,但是在调用 ErrorHandler 之后,既没有更新 HasError 属性 也没有调用 GetErrors 方法,我无法找出为什么会这样。
如前所述,在另一个 window 中,一切正常。
这里是控件的重要部分
public class UnitedStatesCustomaryUnitTextBox : TextBox, INotifyDataErrorInfo
{
static UnitedStatesCustomaryUnitTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(UnitedStatesCustomaryUnitTextBox), new FrameworkPropertyMetadata(typeof(UnitedStatesCustomaryUnitTextBox)));
}
private readonly Dictionary<string, List<string>> _propertyErrors = new Dictionary<string, List<string>>();
#region property Notifications
public static readonly DependencyProperty NotificationsProperty = DependencyProperty.Register(
"Notifications",
typeof(List<Notification>),
typeof(UnitedStatesCustomaryUnitTextBox),
new PropertyMetadata(default(List<Notification>), OnNotificationsChanged));
public List<Notification> Notifications
{
get
{
var result = (List<Notification>)GetValue(NotificationsProperty);
if (result != null)
return result;
result = new List<Notification>();
SetValue(NotificationsProperty, result);
return result;
}
set { SetValue(NotificationsProperty, value); }
}
private static void OnNotificationsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var ctl = sender as UnitedStatesCustomaryUnitTextBox;
if (ctl == null)
return;
var oldValue = e.OldValue as List<Notification>;
var newValue = e.NewValue as List<Notification>;
ctl.OnNotificationsChanged(oldValue, newValue);
}
private void OnNotificationsChanged(List<Notification> oldValue, List<Notification> newValue)
{
Client.Controls.UnitedStatesCustomaryUnitTextBox.UnitedStatesCustomaryUnitTextBox.OnNotificationsChanged
}
#endregion
#region property LengthUomId
public static readonly DependencyProperty LengthUomIdProperty = DependencyProperty.Register(
"LengthUomId",
typeof(int?),
typeof(UnitedStatesCustomaryUnitTextBox),
new PropertyMetadata(default(int?), OnLengthUomIdChanged));
public int? LengthUomId
{
get => (int?)GetValue(LengthUomIdProperty);
set => this.SetValue(LengthUomIdProperty, value);
}
#endregion
#region property Value
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value",
typeof(decimal?),
typeof(UnitedStatesCustomaryUnitTextBox),
new PropertyMetadata(default(decimal?), OnValueChanged));
public decimal? Value
{
get => (decimal?)GetValue(ValueProperty);
set => this.SetValue(ValueProperty, value);
}
private static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var ctl = sender as UnitedStatesCustomaryUnitTextBox;
if (ctl == null)
{
return;
}
var oldValue = e.OldValue as decimal?;
var newValue = e.NewValue as decimal?;
ctl.OnValueChanged(oldValue, newValue);
}
private void OnValueChanged(decimal? oldValue, decimal? newValue)
{
if (!this._isCalculating)
this.SetCurrentValue(TextProperty, this.CalculateFeetInchSixteenth(newValue));
}
#endregion
private bool IsFeetInchSixteenth => Defaults.UomDefaults.DefaultLengthUomId == this.LengthUomId;
protected override void OnTextChanged(TextChangedEventArgs e)
{
this._isCalculating = true;
if (!this.IsFeetInchSixteenth)
{
if (decimal.TryParse(this.Text, out decimal d))
this.Value = d;
return;
}
if (this.ValidateText(this.Text))
this.CalculateValue(this.Text);
this._isCalculating = false;
base.OnTextChanged(e);
}
private bool _isCalculating { get; set; }
private void CalculateValue(string text)
{
var numbers = text.Split('-');
this.Value = Convert.ToDecimal(
int.Parse(numbers[0]) * 192 +
(int.Parse(numbers[1]) * 16) +
(int.Parse(numbers[2]) * 1));
}
private string CalculateFeetInchSixteenth(decimal? value)
{
if (value == null)
return "0-0-0";
var feet = Math.Truncate(value.Value / 192);
var inch = Math.Truncate((value.Value - (feet * 192)) / 16);
var sixteenth = Math.Truncate(value.Value - (feet * 192) - (inch * 16));
return $"{feet}-{inch}-{sixteenth}";
}
private bool ValidateText(string text)
{
this._propertyErrors.Clear();
this.Notifications?.Clear();
this.OnErrorsChanged(nameof(this.Text));
if (string.IsNullOrWhiteSpace(text))
return false;
var numbers = text.Split('-');
if (numbers.Length != 3)
{
var notification = new Notification(
NotificationType.Error,
"FISC0001",
NotificationResources.FISC0001,
NotificationLocalizer.Localize(() => NotificationResources.FISC0001, new object[] { string.Empty }),
null);
this.AddError(nameof(this.Text), notification);
return false;
}
if (!this.CheckNumberRange(numbers))
{
var notification = new Notification(
NotificationType.Error,
"FISC0002",
NotificationResources.FISC0002,
NotificationLocalizer.Localize(() => NotificationResources.FISC0002, new object[] { string.Empty }),
null);
this.AddError(nameof(this.Text), notification);
return false;
}
return true;
}
private bool CheckNumberRange(string[] numbers)
{
if (!int.TryParse(numbers[0], out int number1))
return false;
if (!int.TryParse(numbers[1], out int number2))
return false;
if (!int.TryParse(numbers[2], out int number3))
return false;
return this.IsBetween(number2, 0, 11) && this.IsBetween(number3, 0, 15);
}
[DebuggerStepThrough]
private bool IsBetween(int number, int min, int max)
{
return number >= min && number <= max;
}
public IEnumerable GetErrors(string propertyName)
{
this._propertyErrors.TryGetValue(propertyName, out List<string> errors);
return errors;
}
public bool HasErrors => this._propertyErrors.Any();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public void AddError(string propertyName, Notification notification)
{
if (!this._propertyErrors.ContainsKey(propertyName))
this._propertyErrors.Add(propertyName, new List<string>());
this._propertyErrors[propertyName].Add(notification.LocalizedMessage ?? notification.Message);
this.OnErrorsChanged(propertyName);
this.Notifications.Add(notification);
}
private void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
}
在xaml中我是这样使用控件的
<unitedStatesCustomaryUnitTextBox:UnitedStatesCustomaryUnitTextBox
HorizontalAlignment="Stretch"
Visibility="{Binding IsLengthUnitedStatesCustomaryUnit.Value, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource FalseToCollapsedConverter}}"
Value="{Binding MeasuredLiquidLevelValue.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
LengthUomId="{Binding MeasuredLiquidLevelUom.Value.Id}"
Notifications="{Binding LengthErrors}"
Margin="{DynamicResource DefaultMarginAll}">
<i:Interaction.Behaviors>
<ncshared:LostFocusToCommandBehavior Command="{Binding CalculationRelatedControlLostFocusCommand}" />
</i:Interaction.Behaviors>
</unitedStatesCustomaryUnitTextBox:UnitedStatesCustomaryUnitTextBox>
我还尝试将 ValidateOnDataErrors 等属性设置为 true 但没有任何效果(我认为它们默认为 true)
感谢您的回答。
我实施了您建议的有关 DependencyProperty getter 的更改。
我也找到了解决问题的办法。
设置 ValidateOnNotifyDataErrors 解决了未调用 GetErrors 方法的问题。
似乎我只是忘记为控件设置样式(派生自 Textbox),所以应用程序不知道如何呈现验证错误。
虽然不应该现在可以正常工作了
我写了一个控件,派生自文本框,我可以在其中输入特殊格式的数字。
为确保此格式正确,我还实施了 INotifyDataErrorInfo 以进行验证。 然而,经过几次测试后,一切似乎都很好。错误修复后,验证会弹出并再次消失。
但是现在,我想在另一个 window 中使用相同的控件,但它不再起作用了。验证发生,错误被添加到字典中并调用 OnErrorsChanged,但是在调用 ErrorHandler 之后,既没有更新 HasError 属性 也没有调用 GetErrors 方法,我无法找出为什么会这样。 如前所述,在另一个 window 中,一切正常。
这里是控件的重要部分
public class UnitedStatesCustomaryUnitTextBox : TextBox, INotifyDataErrorInfo
{
static UnitedStatesCustomaryUnitTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(UnitedStatesCustomaryUnitTextBox), new FrameworkPropertyMetadata(typeof(UnitedStatesCustomaryUnitTextBox)));
}
private readonly Dictionary<string, List<string>> _propertyErrors = new Dictionary<string, List<string>>();
#region property Notifications
public static readonly DependencyProperty NotificationsProperty = DependencyProperty.Register(
"Notifications",
typeof(List<Notification>),
typeof(UnitedStatesCustomaryUnitTextBox),
new PropertyMetadata(default(List<Notification>), OnNotificationsChanged));
public List<Notification> Notifications
{
get
{
var result = (List<Notification>)GetValue(NotificationsProperty);
if (result != null)
return result;
result = new List<Notification>();
SetValue(NotificationsProperty, result);
return result;
}
set { SetValue(NotificationsProperty, value); }
}
private static void OnNotificationsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var ctl = sender as UnitedStatesCustomaryUnitTextBox;
if (ctl == null)
return;
var oldValue = e.OldValue as List<Notification>;
var newValue = e.NewValue as List<Notification>;
ctl.OnNotificationsChanged(oldValue, newValue);
}
private void OnNotificationsChanged(List<Notification> oldValue, List<Notification> newValue)
{
Client.Controls.UnitedStatesCustomaryUnitTextBox.UnitedStatesCustomaryUnitTextBox.OnNotificationsChanged
}
#endregion
#region property LengthUomId
public static readonly DependencyProperty LengthUomIdProperty = DependencyProperty.Register(
"LengthUomId",
typeof(int?),
typeof(UnitedStatesCustomaryUnitTextBox),
new PropertyMetadata(default(int?), OnLengthUomIdChanged));
public int? LengthUomId
{
get => (int?)GetValue(LengthUomIdProperty);
set => this.SetValue(LengthUomIdProperty, value);
}
#endregion
#region property Value
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value",
typeof(decimal?),
typeof(UnitedStatesCustomaryUnitTextBox),
new PropertyMetadata(default(decimal?), OnValueChanged));
public decimal? Value
{
get => (decimal?)GetValue(ValueProperty);
set => this.SetValue(ValueProperty, value);
}
private static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var ctl = sender as UnitedStatesCustomaryUnitTextBox;
if (ctl == null)
{
return;
}
var oldValue = e.OldValue as decimal?;
var newValue = e.NewValue as decimal?;
ctl.OnValueChanged(oldValue, newValue);
}
private void OnValueChanged(decimal? oldValue, decimal? newValue)
{
if (!this._isCalculating)
this.SetCurrentValue(TextProperty, this.CalculateFeetInchSixteenth(newValue));
}
#endregion
private bool IsFeetInchSixteenth => Defaults.UomDefaults.DefaultLengthUomId == this.LengthUomId;
protected override void OnTextChanged(TextChangedEventArgs e)
{
this._isCalculating = true;
if (!this.IsFeetInchSixteenth)
{
if (decimal.TryParse(this.Text, out decimal d))
this.Value = d;
return;
}
if (this.ValidateText(this.Text))
this.CalculateValue(this.Text);
this._isCalculating = false;
base.OnTextChanged(e);
}
private bool _isCalculating { get; set; }
private void CalculateValue(string text)
{
var numbers = text.Split('-');
this.Value = Convert.ToDecimal(
int.Parse(numbers[0]) * 192 +
(int.Parse(numbers[1]) * 16) +
(int.Parse(numbers[2]) * 1));
}
private string CalculateFeetInchSixteenth(decimal? value)
{
if (value == null)
return "0-0-0";
var feet = Math.Truncate(value.Value / 192);
var inch = Math.Truncate((value.Value - (feet * 192)) / 16);
var sixteenth = Math.Truncate(value.Value - (feet * 192) - (inch * 16));
return $"{feet}-{inch}-{sixteenth}";
}
private bool ValidateText(string text)
{
this._propertyErrors.Clear();
this.Notifications?.Clear();
this.OnErrorsChanged(nameof(this.Text));
if (string.IsNullOrWhiteSpace(text))
return false;
var numbers = text.Split('-');
if (numbers.Length != 3)
{
var notification = new Notification(
NotificationType.Error,
"FISC0001",
NotificationResources.FISC0001,
NotificationLocalizer.Localize(() => NotificationResources.FISC0001, new object[] { string.Empty }),
null);
this.AddError(nameof(this.Text), notification);
return false;
}
if (!this.CheckNumberRange(numbers))
{
var notification = new Notification(
NotificationType.Error,
"FISC0002",
NotificationResources.FISC0002,
NotificationLocalizer.Localize(() => NotificationResources.FISC0002, new object[] { string.Empty }),
null);
this.AddError(nameof(this.Text), notification);
return false;
}
return true;
}
private bool CheckNumberRange(string[] numbers)
{
if (!int.TryParse(numbers[0], out int number1))
return false;
if (!int.TryParse(numbers[1], out int number2))
return false;
if (!int.TryParse(numbers[2], out int number3))
return false;
return this.IsBetween(number2, 0, 11) && this.IsBetween(number3, 0, 15);
}
[DebuggerStepThrough]
private bool IsBetween(int number, int min, int max)
{
return number >= min && number <= max;
}
public IEnumerable GetErrors(string propertyName)
{
this._propertyErrors.TryGetValue(propertyName, out List<string> errors);
return errors;
}
public bool HasErrors => this._propertyErrors.Any();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public void AddError(string propertyName, Notification notification)
{
if (!this._propertyErrors.ContainsKey(propertyName))
this._propertyErrors.Add(propertyName, new List<string>());
this._propertyErrors[propertyName].Add(notification.LocalizedMessage ?? notification.Message);
this.OnErrorsChanged(propertyName);
this.Notifications.Add(notification);
}
private void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
}
在xaml中我是这样使用控件的
<unitedStatesCustomaryUnitTextBox:UnitedStatesCustomaryUnitTextBox
HorizontalAlignment="Stretch"
Visibility="{Binding IsLengthUnitedStatesCustomaryUnit.Value, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource FalseToCollapsedConverter}}"
Value="{Binding MeasuredLiquidLevelValue.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
LengthUomId="{Binding MeasuredLiquidLevelUom.Value.Id}"
Notifications="{Binding LengthErrors}"
Margin="{DynamicResource DefaultMarginAll}">
<i:Interaction.Behaviors>
<ncshared:LostFocusToCommandBehavior Command="{Binding CalculationRelatedControlLostFocusCommand}" />
</i:Interaction.Behaviors>
</unitedStatesCustomaryUnitTextBox:UnitedStatesCustomaryUnitTextBox>
我还尝试将 ValidateOnDataErrors 等属性设置为 true 但没有任何效果(我认为它们默认为 true)
感谢您的回答。 我实施了您建议的有关 DependencyProperty getter 的更改。
我也找到了解决问题的办法。 设置 ValidateOnNotifyDataErrors 解决了未调用 GetErrors 方法的问题。 似乎我只是忘记为控件设置样式(派生自 Textbox),所以应用程序不知道如何呈现验证错误。
虽然不应该现在可以正常工作了