36 changed files with 816 additions and 41 deletions
@ -0,0 +1,74 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Perspex.Data; |
|||
|
|||
namespace Perspex.Markup.Data.Plugins |
|||
{ |
|||
/// <summary>
|
|||
/// Validates properties that report errors by throwing exceptions.
|
|||
/// </summary>
|
|||
public class ExceptionValidationCheckerPlugin : IValidationCheckerPlugin |
|||
{ |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool Match(WeakReference reference) => true; |
|||
|
|||
|
|||
/// <inheritdoc/>
|
|||
public ValidationCheckerBase Start(WeakReference reference, string name, IPropertyAccessor accessor, Action<ValidationStatus> callback) |
|||
{ |
|||
return new ExceptionValidationChecker(reference, name, accessor, callback); |
|||
} |
|||
|
|||
private class ExceptionValidationChecker : ValidationCheckerBase |
|||
{ |
|||
public ExceptionValidationChecker(WeakReference reference, string name, IPropertyAccessor accessor, Action<ValidationStatus> callback) |
|||
: base(reference, name, accessor, callback) |
|||
{ |
|||
} |
|||
|
|||
public override bool SetValue(object value, BindingPriority priority) |
|||
{ |
|||
try |
|||
{ |
|||
var success = base.SetValue(value, priority); |
|||
SendValidationCallback(new ExceptionValidationStatus(null)); |
|||
return success; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
SendValidationCallback(new ExceptionValidationStatus(ex)); |
|||
} |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Describes the current validation status after setting a property value.
|
|||
/// </summary>
|
|||
public class ExceptionValidationStatus : ValidationStatus |
|||
{ |
|||
internal ExceptionValidationStatus(Exception exception) |
|||
{ |
|||
Exception = exception; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The thrown exception. If there was no thrown exception, null.
|
|||
/// </summary>
|
|||
public Exception Exception { get; } |
|||
|
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool IsValid => Exception == null; |
|||
|
|||
public override bool Match(ValidationMethods enabledMethods) |
|||
{ |
|||
return (enabledMethods & ValidationMethods.Exceptions) != 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
using Perspex.Data; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Perspex.Markup.Data.Plugins |
|||
{ |
|||
/// <summary>
|
|||
/// Defines how view model data validation is observed by an <see cref="ExpressionObserver"/>.
|
|||
/// </summary>
|
|||
public interface IValidationCheckerPlugin |
|||
{ |
|||
|
|||
/// <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 monitering 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="ValidationCheckerBase"/> subclass through which future interactions with the
|
|||
/// property will be made.
|
|||
/// </returns>
|
|||
ValidationCheckerBase Start(WeakReference reference, string name, IPropertyAccessor accessor, Action<ValidationStatus> callback); |
|||
} |
|||
} |
|||
@ -0,0 +1,96 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Perspex.Data; |
|||
using System.ComponentModel; |
|||
using System.Collections; |
|||
using Perspex.Utilities; |
|||
|
|||
namespace Perspex.Markup.Data.Plugins |
|||
{ |
|||
/// <summary>
|
|||
/// Validates properties on objects that implement <see cref="INotifyDataErrorInfo"/>.
|
|||
/// </summary>
|
|||
public class IndeiValidationCheckerPlugin : IValidationCheckerPlugin |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public bool Match(WeakReference reference) |
|||
{ |
|||
return reference.Target is INotifyDataErrorInfo; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public ValidationCheckerBase Start(WeakReference reference, string name, IPropertyAccessor accessor, Action<ValidationStatus> callback) |
|||
{ |
|||
return new IndeiValidationChecker(reference, name, accessor, callback); |
|||
} |
|||
|
|||
private class IndeiValidationChecker : ValidationCheckerBase, IWeakSubscriber<DataErrorsChangedEventArgs> |
|||
{ |
|||
public IndeiValidationChecker(WeakReference reference, string name, IPropertyAccessor accessor, Action<ValidationStatus> callback) |
|||
: base(reference, name, accessor, callback) |
|||
{ |
|||
var target = reference.Target as INotifyDataErrorInfo; |
|||
if (target != null) |
|||
{ |
|||
if (target.HasErrors) |
|||
{ |
|||
SendValidationCallback(new IndeiValidationStatus(target.GetErrors(name))); |
|||
} |
|||
WeakSubscriptionManager.Subscribe( |
|||
target, |
|||
nameof(target.ErrorsChanged), |
|||
this); |
|||
} |
|||
} |
|||
|
|||
public override void Dispose() |
|||
{ |
|||
base.Dispose(); |
|||
var target = _reference.Target as INotifyDataErrorInfo; |
|||
if (target != null) |
|||
{ |
|||
WeakSubscriptionManager.Unsubscribe( |
|||
target, |
|||
nameof(target.ErrorsChanged), |
|||
this); |
|||
} |
|||
} |
|||
|
|||
public void OnEvent(object sender, DataErrorsChangedEventArgs e) |
|||
{ |
|||
if (e.PropertyName == _name || string.IsNullOrEmpty(e.PropertyName)) |
|||
{ |
|||
var indei = _reference.Target as INotifyDataErrorInfo; |
|||
SendValidationCallback(new IndeiValidationStatus(indei.GetErrors(e.PropertyName))); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Describes the current validation status of a property as reported by an object that implements <see cref="INotifyDataErrorInfo"/>.
|
|||
/// </summary>
|
|||
public class IndeiValidationStatus : ValidationStatus |
|||
{ |
|||
internal IndeiValidationStatus(IEnumerable errors) |
|||
{ |
|||
Errors = errors; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool IsValid => !Errors.OfType<object>().Any(); |
|||
|
|||
/// <summary>
|
|||
/// The errors on the given property and on the object as a whole.
|
|||
/// </summary>
|
|||
public IEnumerable Errors { get; } |
|||
|
|||
public override bool Match(ValidationMethods enabledMethods) |
|||
{ |
|||
return (enabledMethods & ValidationMethods.INotifyDataErrorInfo) != 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
using System; |
|||
using Perspex.Data; |
|||
|
|||
namespace Perspex.Markup.Data.Plugins |
|||
{ |
|||
public abstract class ValidationCheckerBase : IPropertyAccessor |
|||
{ |
|||
protected readonly WeakReference _reference; |
|||
protected readonly string _name; |
|||
private readonly IPropertyAccessor _accessor; |
|||
private readonly Action<ValidationStatus> _callback; |
|||
|
|||
protected ValidationCheckerBase(WeakReference reference, string name, IPropertyAccessor accessor, Action<ValidationStatus> callback) |
|||
{ |
|||
_reference = reference; |
|||
_name = name; |
|||
_accessor = accessor; |
|||
_callback = callback; |
|||
} |
|||
|
|||
public Type PropertyType => _accessor.PropertyType; |
|||
|
|||
public object Value => _accessor.Value; |
|||
|
|||
public virtual void Dispose() => _accessor.Dispose(); |
|||
|
|||
public virtual bool SetValue(object value, BindingPriority priority) => _accessor.SetValue(value, priority); |
|||
|
|||
protected void SendValidationCallback(ValidationStatus status) |
|||
{ |
|||
_callback?.Invoke(status); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
|
|||
namespace Perspex.Data |
|||
{ |
|||
[Flags] |
|||
public enum ValidationMethods |
|||
{ |
|||
None = 0, |
|||
Exceptions = 1, |
|||
INotifyDataErrorInfo = 2, |
|||
All = -1 |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// Copyright (c) The Perspex 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; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Perspex.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 abstract class ValidationStatus |
|||
{ |
|||
/// <summary>
|
|||
/// True when the data passes validation; otherwise, false.
|
|||
/// </summary>
|
|||
public abstract bool IsValid { get; } |
|||
|
|||
/// <summary>
|
|||
/// Checks if this validation status came from a currently enabled method of validation checking.
|
|||
/// </summary>
|
|||
/// <param name="enabledMethods">The enabled methods of validation checking.</param>
|
|||
/// <returns>True if enabled; otherwise, false.</returns>
|
|||
public abstract bool Match(ValidationMethods enabledMethods); |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using Perspex.Data; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Perspex.Controls |
|||
{ |
|||
public class ControlValidationStatus : ValidationStatus, INotifyPropertyChanged |
|||
{ |
|||
private Dictionary<Type, ValidationStatus> propertyValidation = new Dictionary<Type, ValidationStatus>(); |
|||
|
|||
public override bool IsValid => propertyValidation.Values.All(status => status.IsValid); |
|||
|
|||
public event PropertyChangedEventHandler PropertyChanged; |
|||
|
|||
public override bool Match(ValidationMethods enabledMethods) => true; |
|||
|
|||
public void UpdateValidationStatus(ValidationStatus status) |
|||
{ |
|||
propertyValidation[status.GetType()] = status; |
|||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("")); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,99 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Perspex.Data; |
|||
using Perspex.Markup.Data.Plugins; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Linq; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Xunit; |
|||
|
|||
namespace Perspex.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 ExceptionValidationCheckerPlugin(); |
|||
var data = new Data(); |
|||
var accessor = inpcAccessorPlugin.Start(new WeakReference(data), nameof(data.NonValidated), _ => { }); |
|||
ValidationStatus 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 ExceptionValidationCheckerPlugin(); |
|||
var data = new Data(); |
|||
var accessor = inpcAccessorPlugin.Start(new WeakReference(data), nameof(data.MustBePositive), _ => { }); |
|||
ValidationStatus 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 ExceptionValidationCheckerPlugin(); |
|||
var data = new Data(); |
|||
var accessor = inpcAccessorPlugin.Start(new WeakReference(data), nameof(data.MustBePositive), _ => { }); |
|||
ValidationStatus 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,120 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
|
|||
using Perspex.Data; |
|||
using Perspex.Markup.Data.Plugins; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Linq; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Xunit; |
|||
using System.Collections; |
|||
|
|||
namespace Perspex.Markup.UnitTests.Data |
|||
{ |
|||
public class IndeiValidatorTests |
|||
{ |
|||
public class Data : INotifyPropertyChanged, INotifyDataErrorInfo |
|||
{ |
|||
private int nonValidated; |
|||
|
|||
public int NonValidated |
|||
{ |
|||
get { return nonValidated; } |
|||
set { nonValidated = value; NotifyPropertyChanged(); } |
|||
} |
|||
|
|||
private int mustBePositive; |
|||
|
|||
public int MustBePositive |
|||
{ |
|||
get { return mustBePositive; } |
|||
set |
|||
{ |
|||
mustBePositive = value; |
|||
NotifyErrorsChanged(); |
|||
} |
|||
} |
|||
|
|||
public bool HasErrors |
|||
{ |
|||
get |
|||
{ |
|||
return MustBePositive > 0; |
|||
} |
|||
} |
|||
|
|||
public event PropertyChangedEventHandler PropertyChanged; |
|||
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; |
|||
|
|||
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") |
|||
{ |
|||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); |
|||
} |
|||
|
|||
private void NotifyErrorsChanged([CallerMemberName] string propertyName = "") |
|||
{ |
|||
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); |
|||
} |
|||
|
|||
public IEnumerable GetErrors(string propertyName) |
|||
{ |
|||
if (propertyName == nameof(MustBePositive) && MustBePositive <= 0) |
|||
{ |
|||
yield return $"{nameof(MustBePositive)} must be positive"; |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Setting_Non_Validating_Does_Not_Trigger_Validation() |
|||
{ |
|||
var inpcAccessorPlugin = new InpcPropertyAccessorPlugin(); |
|||
var validatorPlugin = new IndeiValidationCheckerPlugin(); |
|||
var data = new Data(); |
|||
var accessor = inpcAccessorPlugin.Start(new WeakReference(data), nameof(data.NonValidated), _ => { }); |
|||
ValidationStatus status = null; |
|||
var validator = validatorPlugin.Start(new WeakReference(data), nameof(data.NonValidated), accessor, s => status = s); |
|||
|
|||
validator.SetValue(5, BindingPriority.LocalValue); |
|||
|
|||
Assert.Null(status); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Setting_Validating_Property_To_Valid_Value_Returns_Successful_ValidationStatus() |
|||
{ |
|||
var inpcAccessorPlugin = new InpcPropertyAccessorPlugin(); |
|||
var validatorPlugin = new IndeiValidationCheckerPlugin(); |
|||
var data = new Data(); |
|||
var accessor = inpcAccessorPlugin.Start(new WeakReference(data), nameof(data.MustBePositive), _ => { }); |
|||
ValidationStatus 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 IndeiValidationCheckerPlugin(); |
|||
var data = new Data(); |
|||
var accessor = inpcAccessorPlugin.Start(new WeakReference(data), nameof(data.MustBePositive), _ => { }); |
|||
ValidationStatus 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,111 @@ |
|||
using Perspex.Controls; |
|||
using Perspex.Markup.Xaml.Data; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Linq; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Xunit; |
|||
|
|||
namespace Perspex.Markup.Xaml.UnitTests.Data |
|||
{ |
|||
public class BindingTests_Validation |
|||
{ |
|||
public class Data : INotifyPropertyChanged |
|||
{ |
|||
private string mustbeNonEmpty; |
|||
|
|||
public string MustBeNonEmpty |
|||
{ |
|||
get { return mustbeNonEmpty; } |
|||
set |
|||
{ |
|||
if (string.IsNullOrEmpty(value)) |
|||
{ |
|||
throw new ArgumentException(nameof(value)); |
|||
} |
|||
mustbeNonEmpty = value; |
|||
} |
|||
} |
|||
|
|||
public event PropertyChangedEventHandler PropertyChanged; |
|||
|
|||
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") |
|||
{ |
|||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Disabled_Validation_Should_Not_Trigger_Validation_Change_Direct() |
|||
{ |
|||
var source = new Data { MustBeNonEmpty = "Test" }; |
|||
var target = new TextBlock { DataContext = source }; |
|||
var binding = new Binding |
|||
{ |
|||
Path = nameof(source.MustBeNonEmpty), |
|||
Mode = Perspex.Data.BindingMode.TwoWay, |
|||
ValidationMethods = Perspex.Data.ValidationMethods.None |
|||
}; |
|||
target.Bind(TextBlock.TextProperty, binding); |
|||
|
|||
target.Text = ""; |
|||
|
|||
Assert.True(target.ValidationStatus.IsValid); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Enabled_Validation_Should_Trigger_Validation_Change_Direct() |
|||
{ |
|||
var source = new Data { MustBeNonEmpty = "Test" }; |
|||
var target = new TextBlock { DataContext = source }; |
|||
var binding = new Binding |
|||
{ |
|||
Path = nameof(source.MustBeNonEmpty), |
|||
Mode = Perspex.Data.BindingMode.TwoWay, |
|||
ValidationMethods = Perspex.Data.ValidationMethods.All |
|||
}; |
|||
target.Bind(TextBlock.TextProperty, binding); |
|||
|
|||
target.Text = ""; |
|||
Assert.False(target.ValidationStatus.IsValid); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Disabled_Validation_Should_Not_Trigger_Validation_Change_Styled() |
|||
{ |
|||
var source = new Data { MustBeNonEmpty = "Test" }; |
|||
var target = new TextBlock { DataContext = source }; |
|||
var binding = new Binding |
|||
{ |
|||
Path = nameof(source.MustBeNonEmpty), |
|||
Mode = Perspex.Data.BindingMode.TwoWay, |
|||
ValidationMethods = Perspex.Data.ValidationMethods.None |
|||
}; |
|||
target.Bind(Control.TagProperty, binding); |
|||
|
|||
target.Tag = ""; |
|||
|
|||
Assert.True(target.ValidationStatus.IsValid); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Enabled_Validation_Should_Trigger_Validation_Change_Styled() |
|||
{ |
|||
var source = new Data { MustBeNonEmpty = "Test" }; |
|||
var target = new TextBlock { DataContext = source }; |
|||
var binding = new Binding |
|||
{ |
|||
Path = nameof(source.MustBeNonEmpty), |
|||
Mode = Perspex.Data.BindingMode.TwoWay, |
|||
ValidationMethods = Perspex.Data.ValidationMethods.All |
|||
}; |
|||
target.Bind(Control.TagProperty, binding); |
|||
|
|||
target.Tag = ""; |
|||
Assert.False(target.ValidationStatus.IsValid); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue