diff --git a/src/Markup/Perspex.Markup/Data/ExpressionNode.cs b/src/Markup/Perspex.Markup/Data/ExpressionNode.cs index a120aa8901..ac1cad5b61 100644 --- a/src/Markup/Perspex.Markup/Data/ExpressionNode.cs +++ b/src/Markup/Perspex.Markup/Data/ExpressionNode.cs @@ -107,7 +107,15 @@ namespace Perspex.Markup.Data protected virtual void SendValidationStatus(ValidationStatus status) { - _subject?.OnNext(status); + //Even if elements only bound to sub-values, send validation changes along so they will be surfaced to the UI level. + if (_subject != null) + { + _subject.OnNext(status); + } + else + { + Next.SendValidationStatus(status); + } } protected virtual void Unsubscribe(object target) diff --git a/src/Markup/Perspex.Markup/Data/PropertyAccessorNode.cs b/src/Markup/Perspex.Markup/Data/PropertyAccessorNode.cs index 72cf80e79e..e0561ecfcf 100644 --- a/src/Markup/Perspex.Markup/Data/PropertyAccessorNode.cs +++ b/src/Markup/Perspex.Markup/Data/PropertyAccessorNode.cs @@ -55,10 +55,12 @@ namespace Perspex.Markup.Data if (accessorPlugin != null) { _accessor = accessorPlugin.Start(reference, PropertyName, SetCurrentValue); - var validationPlugin = ExpressionObserver.ValidationCheckers.FirstOrDefault(x => x.Match(reference)); - if (validationPlugin != null) + foreach (var validationPlugin in ExpressionObserver.ValidationCheckers.Where(x => x.Match(reference))) { - _accessor = validationPlugin.Start(reference, PropertyName, _accessor, SendValidationStatus); + if (validationPlugin != null) + { + _accessor = validationPlugin.Start(reference, PropertyName, _accessor, SendValidationStatus); + } } if (_accessor != null) diff --git a/src/Perspex.Controls/Control.cs b/src/Perspex.Controls/Control.cs index f59811b79c..601975a19e 100644 --- a/src/Perspex.Controls/Control.cs +++ b/src/Perspex.Controls/Control.cs @@ -89,8 +89,8 @@ namespace Perspex.Controls /// /// Defines the property. /// - public static readonly DirectProperty ValidationStatusProperty = - PerspexProperty.RegisterDirect(nameof(ValidationStatus), c=> c.ValidationStatus); + public static readonly DirectProperty ValidationStatusProperty = + PerspexProperty.RegisterDirect(nameof(ValidationStatus), c=> c.ValidationStatus); private int _initCount; private string _name; @@ -406,12 +406,12 @@ namespace Perspex.Controls /// protected IPseudoClasses PseudoClasses => Classes; - private ValidationStatus validationStatus; + private ControlValidationStatus validationStatus = new ControlValidationStatus(); /// /// The current validation status of the control. /// - public ValidationStatus ValidationStatus + public ControlValidationStatus ValidationStatus { get { @@ -427,7 +427,7 @@ namespace Perspex.Controls protected override void DataValidation(PerspexProperty property, ValidationStatus status) { base.DataValidation(property, status); - ValidationStatus = status; + ValidationStatus.UpdateValidationStatus(status); } /// diff --git a/src/Perspex.Controls/ControlValidationStatus.cs b/src/Perspex.Controls/ControlValidationStatus.cs new file mode 100644 index 0000000000..aad65a1a0d --- /dev/null +++ b/src/Perspex.Controls/ControlValidationStatus.cs @@ -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 propertyValidation = new Dictionary(); + + 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("")); + } + } +} diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj index 8c62f1d66c..3bf7b4be9c 100644 --- a/src/Perspex.Controls/Perspex.Controls.csproj +++ b/src/Perspex.Controls/Perspex.Controls.csproj @@ -46,6 +46,7 @@ + diff --git a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj index 932af42724..47635bf006 100644 --- a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj +++ b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj @@ -196,9 +196,6 @@ - - - diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_Validation.cs b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_Validation.cs index 799486bd5f..551f930406 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_Validation.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_Validation.cs @@ -13,7 +13,6 @@ namespace Perspex.Markup.Xaml.UnitTests.Data { public class BindingTests_Validation { - public class Data : INotifyPropertyChanged { private string mustbeNonEmpty; @@ -54,7 +53,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data target.Text = ""; - Assert.Null(target.ValidationStatus); + Assert.True(target.ValidationStatus.IsValid); } [Fact] @@ -71,7 +70,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data target.Bind(TextBlock.TextProperty, binding); target.Text = ""; - Assert.NotNull(target.ValidationStatus); + Assert.False(target.ValidationStatus.IsValid); } [Fact] @@ -89,7 +88,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data target.Tag = ""; - Assert.Null(target.ValidationStatus); + Assert.True(target.ValidationStatus.IsValid); } [Fact] @@ -106,7 +105,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data target.Bind(Control.TagProperty, binding); target.Tag = ""; - Assert.NotNull(target.ValidationStatus); + Assert.False(target.ValidationStatus.IsValid); } } }