diff --git a/src/Markup/Perspex.Markup/Data/Plugins/IPropertyAccessorPlugin.cs b/src/Markup/Perspex.Markup/Data/Plugins/IPropertyAccessorPlugin.cs
index 8a431041d0..9dd4f10dbe 100644
--- a/src/Markup/Perspex.Markup/Data/Plugins/IPropertyAccessorPlugin.cs
+++ b/src/Markup/Perspex.Markup/Data/Plugins/IPropertyAccessorPlugin.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Collections;
namespace Perspex.Markup.Data.Plugins
{
@@ -24,6 +25,7 @@ namespace Perspex.Markup.Data.Plugins
/// A weak reference to the object.
/// The property name.
/// A function to call when the property changes.
+ /// A function to call when the validation status of the property changes.
///
/// An interface through which future interactions with the
/// property will be made.
@@ -31,6 +33,7 @@ namespace Perspex.Markup.Data.Plugins
IPropertyAccessor Start(
WeakReference reference,
string propertyName,
- Action changed);
+ Action changed,
+ Action validationChanged);
}
}
diff --git a/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs b/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs
index 64421ddc57..9eee066c61 100644
--- a/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs
+++ b/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs
@@ -9,6 +9,7 @@ using System.Reflection;
using Perspex.Data;
using Perspex.Logging;
using Perspex.Utilities;
+using System.Collections;
namespace Perspex.Markup.Data.Plugins
{
@@ -36,6 +37,7 @@ namespace Perspex.Markup.Data.Plugins
/// The object.
/// The property name.
/// A function to call when the property changes.
+ /// A function to call when the validation state of the property changes.
///
/// An interface through which future interactions with the
/// property will be made.
@@ -43,7 +45,8 @@ namespace Perspex.Markup.Data.Plugins
public IPropertyAccessor Start(
WeakReference reference,
string propertyName,
- Action changed)
+ Action changed,
+ Action validationChanged)
{
Contract.Requires(reference != null);
Contract.Requires(propertyName != null);
@@ -54,7 +57,7 @@ namespace Perspex.Markup.Data.Plugins
if (p != null)
{
- return new Accessor(reference, p, changed);
+ return new Accessor(reference, p, changed, validationChanged);
}
else
{
@@ -64,16 +67,18 @@ namespace Perspex.Markup.Data.Plugins
}
}
- private class Accessor : IPropertyAccessor, IWeakSubscriber
+ private class Accessor : IPropertyAccessor, IWeakSubscriber, IWeakSubscriber
{
private readonly WeakReference _reference;
private readonly PropertyInfo _property;
private readonly Action _changed;
+ private readonly Action _validationChanged;
public Accessor(
WeakReference reference,
PropertyInfo property,
- Action changed)
+ Action changed,
+ Action validationChanged)
{
Contract.Requires(reference != null);
Contract.Requires(property != null);
@@ -81,12 +86,13 @@ namespace Perspex.Markup.Data.Plugins
_reference = reference;
_property = property;
_changed = changed;
+ _validationChanged = validationChanged;
var inpc = reference.Target as INotifyPropertyChanged;
if (inpc != null)
{
- WeakSubscriptionManager.Subscribe(
+ WeakSubscriptionManager.Subscribe(
inpc,
nameof(inpc.PropertyChanged),
this);
@@ -101,6 +107,19 @@ namespace Perspex.Markup.Data.Plugins
reference.Target,
reference.Target.GetType());
}
+
+ var indei = _reference.Target as INotifyDataErrorInfo;
+ if (indei != null)
+ {
+ if (indei.HasErrors)
+ {
+ _validationChanged(indei.GetErrors(property.Name));
+ }
+ WeakSubscriptionManager.Subscribe(
+ indei,
+ nameof(indei.ErrorsChanged),
+ this);
+ }
}
public Type PropertyType => _property.PropertyType;
@@ -113,11 +132,20 @@ namespace Perspex.Markup.Data.Plugins
if (inpc != null)
{
- WeakSubscriptionManager.Unsubscribe(
+ WeakSubscriptionManager.Unsubscribe(
inpc,
nameof(inpc.PropertyChanged),
this);
}
+
+ var indei = _reference.Target as INotifyDataErrorInfo;
+ if (indei != null)
+ {
+ WeakSubscriptionManager.Unsubscribe(
+ indei,
+ nameof(indei.ErrorsChanged),
+ this);
+ }
}
public bool SetValue(object value, BindingPriority priority)
@@ -131,13 +159,22 @@ namespace Perspex.Markup.Data.Plugins
return false;
}
- public void OnEvent(object sender, PropertyChangedEventArgs e)
+ void IWeakSubscriber.OnEvent(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName))
{
_changed(Value);
}
}
+
+ void IWeakSubscriber.OnEvent(object sender, DataErrorsChangedEventArgs e)
+ {
+ if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName))
+ {
+ var indei = _reference.Target as INotifyDataErrorInfo;
+ _validationChanged(indei.GetErrors(e.PropertyName));
+ }
+ }
}
}
}
diff --git a/src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs b/src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs
index bc16989a7a..d27d1e8be4 100644
--- a/src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs
+++ b/src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs
@@ -31,6 +31,7 @@ namespace Perspex.Markup.Data.Plugins
/// A weak reference to the object.
/// The property name.
/// A function to call when the property changes.
+ /// A function to call when the validation state of the property changes.
///
/// An interface through which future interactions with the
/// property will be made.
@@ -38,7 +39,8 @@ namespace Perspex.Markup.Data.Plugins
public IPropertyAccessor Start(
WeakReference reference,
string propertyName,
- Action changed)
+ Action changed,
+ Action validationChanged)
{
Contract.Requires(reference != null);
Contract.Requires(propertyName != null);
diff --git a/src/Markup/Perspex.Markup/Data/PropertyAccessorNode.cs b/src/Markup/Perspex.Markup/Data/PropertyAccessorNode.cs
index 14883e449a..0f76503ff3 100644
--- a/src/Markup/Perspex.Markup/Data/PropertyAccessorNode.cs
+++ b/src/Markup/Perspex.Markup/Data/PropertyAccessorNode.cs
@@ -54,7 +54,7 @@ namespace Perspex.Markup.Data
if (plugin != null)
{
- _accessor = plugin.Start(reference, PropertyName, SetCurrentValue);
+ _accessor = plugin.Start(reference, PropertyName, SetCurrentValue, _ => { });
if (_accessor != null)
{
diff --git a/tests/Perspex.Markup.UnitTests/Data/InpcPluginTests.cs b/tests/Perspex.Markup.UnitTests/Data/InpcPluginTests.cs
new file mode 100644
index 0000000000..3a32e3eabc
--- /dev/null
+++ b/tests/Perspex.Markup.UnitTests/Data/InpcPluginTests.cs
@@ -0,0 +1,139 @@
+using Perspex.Markup.Data.Plugins;
+using System;
+using System.Collections;
+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 InpcPluginTests
+ {
+ private class InpcTest : INotifyPropertyChanged, INotifyDataErrorInfo
+ {
+ private int noValidationTest;
+
+ public int NoValidationTest
+ {
+ get { return noValidationTest; }
+ set { noValidationTest = value; NotifyPropertyChanged(); }
+ }
+
+ public bool HasErrors
+ {
+ get
+ {
+ return NonNegative < 0;
+ }
+ }
+
+ private int nonNegative;
+
+ public int NonNegative
+ {
+ get { return nonNegative; }
+ set
+ {
+ var old = nonNegative;
+ nonNegative = value;
+ NotifyPropertyChanged();
+ if (old * value < 0) // If signs are different
+ {
+ NotifyErrorsChanged();
+ }
+ }
+ }
+
+
+ public event EventHandler ErrorsChanged;
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public IEnumerable GetErrors(string propertyName)
+ {
+ if (string.IsNullOrEmpty(propertyName) || propertyName == nameof(NonNegative))
+ {
+ if (NonNegative < 0)
+ {
+ yield return "Invalid Value";
+ }
+ }
+ }
+
+ private void NotifyPropertyChanged([CallerMemberName] string property = "")
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
+ }
+
+ private void NotifyErrorsChanged([CallerMemberName] string property = "")
+ {
+ ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(property));
+ }
+ }
+
+ [Fact]
+ public void Calls_Change_Callback_When_Value_Changes()
+ {
+ var plugin = new InpcPropertyAccessorPlugin();
+ var source = new InpcTest { NoValidationTest = 0 };
+ var changeFired = false;
+ plugin.Start(new WeakReference(source), nameof(InpcTest.NoValidationTest), _ => changeFired = true, _ => { });
+ source.NoValidationTest = 1;
+
+ Assert.True(changeFired);
+ }
+
+ [Fact]
+ public void ValidationChanged_Does_Not_Fire_When_NonValidated_Value_Changes()
+ {
+ var plugin = new InpcPropertyAccessorPlugin();
+ var source = new InpcTest { NoValidationTest = 0 };
+ var validationFired = false;
+ plugin.Start(new WeakReference(source), nameof(InpcTest.NoValidationTest), _ => { }, _ => validationFired = true);
+ source.NoValidationTest = 1;
+
+ Assert.False(validationFired);
+ }
+
+ [Fact]
+ public void ValidationChanged_Does_Not_Fire_When_Validation_Does_Not_Change()
+ {
+ var plugin = new InpcPropertyAccessorPlugin();
+ var source = new InpcTest { NonNegative = 3 };
+ var validationFired = false;
+ plugin.Start(new WeakReference(source), nameof(InpcTest.NonNegative), _ => { }, _ => validationFired = true);
+ source.NonNegative = 5;
+
+ Assert.False(validationFired);
+ }
+
+ [Fact]
+ public void ValidationChanged_Fires_On_Start_If_Has_Errors()
+ {
+ var plugin = new InpcPropertyAccessorPlugin();
+ var source = new InpcTest { NonNegative = -5 };
+
+ Assert.True(source.HasErrors);
+
+ var validationFired = false;
+ plugin.Start(new WeakReference(source), nameof(InpcTest.NonNegative), _ => { }, _ => validationFired = true);
+ Assert.True(validationFired);
+ }
+
+
+
+ [Fact]
+ public void ValidationChanged_Fires_When_Validation_Changes()
+ {
+ var plugin = new InpcPropertyAccessorPlugin();
+ var source = new InpcTest { NonNegative = 5 };
+ var validationFired = false;
+ plugin.Start(new WeakReference(source), nameof(InpcTest.NonNegative), _ => { }, _ => validationFired = true);
+ source.NonNegative = -1;
+ Assert.True(validationFired);
+ }
+ }
+}
diff --git a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj
index f32a584d1d..fa2b551600 100644
--- a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj
+++ b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj
@@ -97,6 +97,7 @@
+