我编写了一个源自 Textbox 的控件,我可以在其中输入特殊格式的数字。
为了确保这种格式是正确的,我还实现了 INotifyDataErrorInfo 进行验证。然而,经过几次测试,一切似乎都很好。修复错误后,验证会弹出并再次消失。
但是现在,我想在另一个窗口中使用相同的控件,但它不再起作用了。验证发生后,错误被添加到字典中并调用 OnErrorsChanged,但在调用 ErrorHandler 之后,既没有更新 HasError 属性,也没有调用 GetErrors 方法,我不知道为什么会这样。如前所述,在另一个窗口中,一切都按预期工作。
这是控制的重要部分
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)