34 changed files with 789 additions and 565 deletions
@ -1,17 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Avalonia.Data |
|||
{ |
|||
/// <summary>
|
|||
/// Contains information on if the current object passed validation.
|
|||
/// Subclasses of this class contain additional information depending on the method of validation checking.
|
|||
/// </summary>
|
|||
public interface IValidationStatus |
|||
{ |
|||
/// <summary>
|
|||
/// True when the data passes validation; otherwise, false.
|
|||
/// </summary>
|
|||
bool IsValid { get; } |
|||
} |
|||
} |
|||
@ -1,44 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Avalonia.Data |
|||
{ |
|||
/// <summary>
|
|||
/// An immutable struct that contains validation information for a <see cref="AvaloniaObject"/> that validates a single property.
|
|||
/// </summary>
|
|||
public struct ObjectValidationStatus : IValidationStatus |
|||
{ |
|||
private Dictionary<Type, IValidationStatus> currentValidationStatus; |
|||
|
|||
public bool IsValid => currentValidationStatus?.Values.All(status => status.IsValid) ?? true; |
|||
|
|||
/// <summary>
|
|||
/// Constructs the structure with the given validation information.
|
|||
/// </summary>
|
|||
/// <param name="validations">The validation information</param>
|
|||
public ObjectValidationStatus(Dictionary<Type, IValidationStatus> validations) |
|||
:this() |
|||
{ |
|||
currentValidationStatus = validations; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new status with the updated information.
|
|||
/// </summary>
|
|||
/// <param name="status">The updated status information.</param>
|
|||
/// <returns>The new validation status.</returns>
|
|||
public ObjectValidationStatus UpdateValidationStatus(IValidationStatus status) |
|||
{ |
|||
var newStatus = new Dictionary<Type, IValidationStatus>(currentValidationStatus ?? |
|||
new Dictionary<Type, IValidationStatus>()); |
|||
newStatus[status.GetType()] = status; |
|||
return new ObjectValidationStatus(newStatus); |
|||
} |
|||
|
|||
public IEnumerable<IValidationStatus> StatusInformation => currentValidationStatus.Values; |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia.Data; |
|||
|
|||
namespace Avalonia.Markup.Data.Plugins |
|||
{ |
|||
/// <summary>
|
|||
/// Base class for data validators.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Data validators are <see cref="IPropertyAccessor"/>s that are returned from an
|
|||
/// <see cref="IDataValidationPlugin"/>. They wrap an inner <see cref="IPropertyAccessor"/>
|
|||
/// and convert any values received from the inner property accessor into
|
|||
/// <see cref="BindingNotification"/>s.
|
|||
/// </remarks>
|
|||
public abstract class DataValidatiorBase : PropertyAccessorBase, IObserver<object> |
|||
{ |
|||
private readonly IPropertyAccessor _inner; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="DataValidatiorBase"/> class.
|
|||
/// </summary>
|
|||
/// <param name="inner">The inner property accessor.</param>
|
|||
protected DataValidatiorBase(IPropertyAccessor inner) |
|||
{ |
|||
_inner = inner; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override Type PropertyType => _inner.PropertyType; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override object Value => _inner.Value; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool SetValue(object value, BindingPriority priority) => _inner.SetValue(value, priority); |
|||
|
|||
/// <summary>
|
|||
/// Should never be called: the inner <see cref="IPropertyAccessor"/> should never notify
|
|||
/// completion.
|
|||
/// </summary>
|
|||
void IObserver<object>.OnCompleted() { } |
|||
|
|||
/// <summary>
|
|||
/// Should never be called: the inner <see cref="IPropertyAccessor"/> should never notify
|
|||
/// an error.
|
|||
/// </summary>
|
|||
void IObserver<object>.OnError(Exception error) { } |
|||
|
|||
/// <summary>
|
|||
/// Called when the inner <see cref="IPropertyAccessor"/> notifies with a new value.
|
|||
/// </summary>
|
|||
/// <param name="value">The value.</param>
|
|||
void IObserver<object>.OnNext(object value) => InnerValueChanged(value); |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Dispose(bool disposing) => _inner.Dispose(); |
|||
|
|||
/// <summary>
|
|||
/// Begins listening to the inner <see cref="IPropertyAccessor"/>.
|
|||
/// </summary>
|
|||
protected override void SubscribeCore(IObserver<object> observer) => _inner.Subscribe(this); |
|||
|
|||
/// <summary>
|
|||
/// Called when the inner <see cref="IPropertyAccessor"/> notifies with a new value.
|
|||
/// </summary>
|
|||
/// <param name="value">The value.</param>
|
|||
/// <remarks>
|
|||
/// Notifies the observer that the value has changed. The value will be wrapped in a
|
|||
/// <see cref="BindingNotification"/> if it is not already a binding notification.
|
|||
/// </remarks>
|
|||
protected virtual void InnerValueChanged(object value) |
|||
{ |
|||
var notification = value as BindingNotification ?? new BindingNotification(value); |
|||
Observer.OnNext(notification); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia.Data; |
|||
|
|||
namespace Avalonia.Markup.Data.Plugins |
|||
{ |
|||
/// <summary>
|
|||
/// Defines how data validation is observed by an <see cref="ExpressionObserver"/>.
|
|||
/// </summary>
|
|||
public interface IDataValidationPlugin |
|||
{ |
|||
/// <summary>
|
|||
/// Checks whether this plugin can handle data validation on the specified object.
|
|||
/// </summary>
|
|||
/// <param name="reference">A weak reference to the object.</param>
|
|||
/// <returns>True if the plugin can handle the object; otherwise false.</returns>
|
|||
bool Match(WeakReference reference); |
|||
|
|||
/// <summary>
|
|||
/// Starts monitoring the data validation state of a property on an object.
|
|||
/// </summary>
|
|||
/// <param name="reference">A weak reference to the object.</param>
|
|||
/// <param name="propertyName">The property name.</param>
|
|||
/// <param name="inner">The inner property accessor used to aceess the property.</param>
|
|||
/// <returns>
|
|||
/// An <see cref="IPropertyAccessor"/> interface through which future interactions with the
|
|||
/// property will be made.
|
|||
/// </returns>
|
|||
IPropertyAccessor Start( |
|||
WeakReference reference, |
|||
string propertyName, |
|||
IPropertyAccessor inner); |
|||
} |
|||
} |
|||
@ -1,35 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia.Data; |
|||
|
|||
namespace Avalonia.Markup.Data.Plugins |
|||
{ |
|||
/// <summary>
|
|||
/// Defines how view model data validation is observed by an <see cref="ExpressionObserver"/>.
|
|||
/// </summary>
|
|||
public interface IValidationPlugin |
|||
{ |
|||
|
|||
/// <summary>
|
|||
/// Checks whether the data uses a validation scheme supported by this plugin.
|
|||
/// </summary>
|
|||
/// <param name="reference">A weak reference to the data.</param>
|
|||
/// <returns><c>true</c> if this plugin can observe the validation; otherwise, <c>false</c>.</returns>
|
|||
bool Match(WeakReference reference); |
|||
|
|||
/// <summary>
|
|||
/// Starts monitoring the validation state of an object for the given property.
|
|||
/// </summary>
|
|||
/// <param name="reference">A weak reference to the object.</param>
|
|||
/// <param name="name">The property name.</param>
|
|||
/// <param name="accessor">An underlying <see cref="IPropertyAccessor"/> to access the property.</param>
|
|||
/// <param name="callback">A function to call when the validation state changes.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="ValidatingPropertyAccessorBase"/> subclass through which future interactions with the
|
|||
/// property will be made.
|
|||
/// </returns>
|
|||
IPropertyAccessor Start(WeakReference reference, string name, IPropertyAccessor accessor, Action<IValidationStatus> callback); |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia.Data; |
|||
|
|||
namespace Avalonia.Markup.Data.Plugins |
|||
{ |
|||
/// <summary>
|
|||
/// Defines a default base implementation for a <see cref="IPropertyAccessor"/>.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// <see cref="IPropertyAccessor"/> is an observable that will only be subscribed to one time.
|
|||
/// In addition, the subscription can be disposed by calling <see cref="Dispose()"/> on the
|
|||
/// property accessor itself - this prevents needing to hold two references for a subscription.
|
|||
/// </remarks>
|
|||
public abstract class PropertyAccessorBase : IPropertyAccessor |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public abstract Type PropertyType { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public abstract object Value { get; } |
|||
|
|||
/// <summary>
|
|||
/// Stops the subscription.
|
|||
/// </summary>
|
|||
public void Dispose() => Dispose(true); |
|||
|
|||
/// <inheritdoc/>
|
|||
public abstract bool SetValue(object value, BindingPriority priority); |
|||
|
|||
/// <summary>
|
|||
/// The currently subscribed observer.
|
|||
/// </summary>
|
|||
protected IObserver<object> Observer { get; private set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public IDisposable Subscribe(IObserver<object> observer) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(observer != null); |
|||
|
|||
if (Observer != null) |
|||
{ |
|||
throw new InvalidOperationException( |
|||
"A property accessor can be subscribed to only once."); |
|||
} |
|||
|
|||
Observer = observer; |
|||
SubscribeCore(observer); |
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Stops listening to the property.
|
|||
/// </summary>
|
|||
/// <param name="disposing">
|
|||
/// True if the <see cref="Dispose()"/> method was called, false if the object is being
|
|||
/// finalized.
|
|||
/// </param>
|
|||
protected virtual void Dispose(bool disposing) => Observer = null; |
|||
|
|||
/// <summary>
|
|||
/// When overridden in a derived class, begins listening to the property.
|
|||
/// </summary>
|
|||
protected abstract void SubscribeCore(IObserver<object> observer); |
|||
} |
|||
} |
|||
@ -1,46 +0,0 @@ |
|||
using System; |
|||
using Avalonia.Data; |
|||
|
|||
namespace Avalonia.Markup.Data.Plugins |
|||
{ |
|||
|
|||
/// <summary>
|
|||
/// A base class for validating <see cref="IPropertyAccessor"/>s that wraps an <see cref="IPropertyAccessor"/> and forwards method calls to it.
|
|||
/// </summary>
|
|||
public abstract class ValidatingPropertyAccessorBase : IPropertyAccessor |
|||
{ |
|||
protected readonly WeakReference _reference; |
|||
protected readonly string _name; |
|||
private readonly IPropertyAccessor _accessor; |
|||
private readonly Action<IValidationStatus> _callback; |
|||
|
|||
protected ValidatingPropertyAccessorBase(WeakReference reference, string name, IPropertyAccessor accessor, Action<IValidationStatus> callback) |
|||
{ |
|||
_reference = reference; |
|||
_name = name; |
|||
_accessor = accessor; |
|||
_callback = callback; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Type PropertyType => _accessor.PropertyType; |
|||
|
|||
/// <inheritdoc/>
|
|||
public object Value => _accessor.Value; |
|||
|
|||
/// <inheritdoc/>
|
|||
public virtual void Dispose() => _accessor.Dispose(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public virtual bool SetValue(object value, BindingPriority priority) => _accessor.SetValue(value, priority); |
|||
|
|||
/// <summary>
|
|||
/// Sends the validation status to the callback specified in construction.
|
|||
/// </summary>
|
|||
/// <param name="status">The validation status.</param>
|
|||
protected void SendValidationCallback(IValidationStatus status) |
|||
{ |
|||
_callback?.Invoke(status); |
|||
} |
|||
} |
|||
} |
|||
@ -1,93 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Runtime.CompilerServices; |
|||
using Avalonia.Data; |
|||
using Avalonia.Markup.Data.Plugins; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Markup.UnitTests.Data |
|||
{ |
|||
public class ExceptionValidatorTests |
|||
{ |
|||
public class Data : INotifyPropertyChanged |
|||
{ |
|||
private int nonValidated; |
|||
|
|||
public int NonValidated |
|||
{ |
|||
get { return nonValidated; } |
|||
set { nonValidated = value; NotifyPropertyChanged(); } |
|||
} |
|||
|
|||
private int mustBePositive; |
|||
|
|||
public int MustBePositive |
|||
{ |
|||
get { return mustBePositive; } |
|||
set |
|||
{ |
|||
if (value <= 0) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(value)); |
|||
} |
|||
mustBePositive = value; |
|||
} |
|||
} |
|||
|
|||
public event PropertyChangedEventHandler PropertyChanged; |
|||
|
|||
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") |
|||
{ |
|||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Setting_Non_Validating_Triggers_Validation() |
|||
{ |
|||
var inpcAccessorPlugin = new InpcPropertyAccessorPlugin(); |
|||
var validatorPlugin = new ExceptionValidationPlugin(); |
|||
var data = new Data(); |
|||
var accessor = inpcAccessorPlugin.Start(new WeakReference(data), nameof(data.NonValidated), _ => { }); |
|||
IValidationStatus status = null; |
|||
var validator = validatorPlugin.Start(new WeakReference(data), nameof(data.NonValidated), accessor, s => status = s); |
|||
|
|||
validator.SetValue(5, BindingPriority.LocalValue); |
|||
|
|||
Assert.NotNull(status); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Setting_Validating_Property_To_Valid_Value_Returns_Successful_ValidationStatus() |
|||
{ |
|||
var inpcAccessorPlugin = new InpcPropertyAccessorPlugin(); |
|||
var validatorPlugin = new ExceptionValidationPlugin(); |
|||
var data = new Data(); |
|||
var accessor = inpcAccessorPlugin.Start(new WeakReference(data), nameof(data.MustBePositive), _ => { }); |
|||
IValidationStatus status = null; |
|||
var validator = validatorPlugin.Start(new WeakReference(data), nameof(data.MustBePositive), accessor, s => status = s); |
|||
|
|||
validator.SetValue(5, BindingPriority.LocalValue); |
|||
|
|||
Assert.True(status.IsValid); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Setting_Validating_Property_To_Invalid_Value_Returns_Failed_ValidationStatus() |
|||
{ |
|||
var inpcAccessorPlugin = new InpcPropertyAccessorPlugin(); |
|||
var validatorPlugin = new ExceptionValidationPlugin(); |
|||
var data = new Data(); |
|||
var accessor = inpcAccessorPlugin.Start(new WeakReference(data), nameof(data.MustBePositive), _ => { }); |
|||
IValidationStatus status = null; |
|||
var validator = validatorPlugin.Start(new WeakReference(data), nameof(data.MustBePositive), accessor, s => status = s); |
|||
|
|||
validator.SetValue(-5, BindingPriority.LocalValue); |
|||
|
|||
Assert.False(status.IsValid); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Reactive.Linq; |
|||
using System.Runtime.CompilerServices; |
|||
using Avalonia.Data; |
|||
using Avalonia.Markup.Data.Plugins; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Markup.UnitTests.Data.Plugins |
|||
{ |
|||
public class ExceptionValidationPluginTests |
|||
{ |
|||
[Fact] |
|||
public void Produces_BindingNotifications() |
|||
{ |
|||
var inpcAccessorPlugin = new InpcPropertyAccessorPlugin(); |
|||
var validatorPlugin = new ExceptionValidationPlugin(); |
|||
var data = new Data(); |
|||
var accessor = inpcAccessorPlugin.Start(new WeakReference(data), nameof(data.MustBePositive)); |
|||
var validator = validatorPlugin.Start(new WeakReference(data), nameof(data.MustBePositive), accessor); |
|||
var result = new List<object>(); |
|||
|
|||
validator.Subscribe(x => result.Add(x)); |
|||
validator.SetValue(5, BindingPriority.LocalValue); |
|||
validator.SetValue(-2, BindingPriority.LocalValue); |
|||
validator.SetValue(6, BindingPriority.LocalValue); |
|||
|
|||
Assert.Equal(new[] |
|||
{ |
|||
new BindingNotification(0), |
|||
new BindingNotification(5), |
|||
new BindingNotification(new ArgumentOutOfRangeException("value"), BindingErrorType.DataValidationError), |
|||
new BindingNotification(6), |
|||
}, result); |
|||
} |
|||
|
|||
public class Data : INotifyPropertyChanged |
|||
{ |
|||
private int _mustBePositive; |
|||
|
|||
public int MustBePositive |
|||
{ |
|||
get { return _mustBePositive; } |
|||
set |
|||
{ |
|||
if (value <= 0) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(value)); |
|||
} |
|||
|
|||
if (value != _mustBePositive) |
|||
{ |
|||
_mustBePositive = value; |
|||
NotifyPropertyChanged(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public event PropertyChangedEventHandler PropertyChanged; |
|||
|
|||
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") |
|||
{ |
|||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,138 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Reactive.Linq; |
|||
using Avalonia.Data; |
|||
using Avalonia.Markup.Data.Plugins; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Markup.UnitTests.Data.Plugins |
|||
{ |
|||
public class IndeiValidationPluginTests |
|||
{ |
|||
[Fact] |
|||
public void Produces_BindingNotifications() |
|||
{ |
|||
var inpcAccessorPlugin = new InpcPropertyAccessorPlugin(); |
|||
var validatorPlugin = new IndeiValidationPlugin(); |
|||
var data = new Data { Maximum = 5 }; |
|||
var accessor = inpcAccessorPlugin.Start(new WeakReference(data), nameof(data.Value)); |
|||
var validator = validatorPlugin.Start(new WeakReference(data), nameof(data.Value), accessor); |
|||
var result = new List<object>(); |
|||
|
|||
validator.Subscribe(x => result.Add(x)); |
|||
validator.SetValue(5, BindingPriority.LocalValue); |
|||
validator.SetValue(6, BindingPriority.LocalValue); |
|||
data.Maximum = 10; |
|||
data.Maximum = 5; |
|||
|
|||
Assert.Equal(new[] |
|||
{ |
|||
new BindingNotification(0), |
|||
new BindingNotification(5), |
|||
|
|||
// Value is first signalled without an error as validation hasn't been updated.
|
|||
new BindingNotification(6), |
|||
|
|||
// Then the ErrorsChanged event is fired.
|
|||
new BindingNotification(new Exception("Must be less than Maximum"), BindingErrorType.DataValidationError, 6), |
|||
|
|||
// Maximum is changed to 10 so value is now valid.
|
|||
new BindingNotification(6), |
|||
|
|||
// And Maximum is changed back to 5.
|
|||
new BindingNotification(new Exception("Must be less than Maximum"), BindingErrorType.DataValidationError, 6), |
|||
}, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Subscribes_And_Unsubscribes() |
|||
{ |
|||
var inpcAccessorPlugin = new InpcPropertyAccessorPlugin(); |
|||
var validatorPlugin = new IndeiValidationPlugin(); |
|||
var data = new Data { Maximum = 5 }; |
|||
var accessor = inpcAccessorPlugin.Start(new WeakReference(data), nameof(data.Value)); |
|||
var validator = validatorPlugin.Start(new WeakReference(data), nameof(data.Value), accessor); |
|||
|
|||
Assert.Equal(0, data.SubscriptionCount); |
|||
var sub = validator.Subscribe(_ => { }); |
|||
Assert.Equal(1, data.SubscriptionCount); |
|||
sub.Dispose(); |
|||
Assert.Equal(0, data.SubscriptionCount); |
|||
} |
|||
|
|||
public class Data : INotifyDataErrorInfo, INotifyPropertyChanged |
|||
{ |
|||
private int _value; |
|||
private int _maximum; |
|||
private string _error; |
|||
private EventHandler<DataErrorsChangedEventArgs> _errorsChanged; |
|||
|
|||
public bool HasErrors => _error != null; |
|||
public int SubscriptionCount { get; private set; } |
|||
|
|||
public int Value |
|||
{ |
|||
get { return _value; } |
|||
set |
|||
{ |
|||
_value = value; |
|||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value))); |
|||
UpdateError(); |
|||
} |
|||
} |
|||
|
|||
public int Maximum |
|||
{ |
|||
get { return _maximum; } |
|||
set |
|||
{ |
|||
_maximum = value; |
|||
UpdateError(); |
|||
} |
|||
} |
|||
|
|||
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged |
|||
{ |
|||
add { _errorsChanged += value; ++SubscriptionCount; } |
|||
remove { _errorsChanged -= value; --SubscriptionCount; } |
|||
} |
|||
|
|||
public event PropertyChangedEventHandler PropertyChanged; |
|||
|
|||
public IEnumerable GetErrors(string propertyName) |
|||
{ |
|||
if (propertyName == nameof(Value) && _error != null) |
|||
{ |
|||
return new[] { _error }; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private void UpdateError() |
|||
{ |
|||
if (_value <= _maximum) |
|||
{ |
|||
if (_error != null) |
|||
{ |
|||
_error = null; |
|||
_errorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(Value))); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
if (_error == null) |
|||
{ |
|||
_error = "Must be less than Maximum"; |
|||
_errorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(Value))); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue