Browse Source

Added support for IPropertyAccessor's to listen for error notifications from a target that implements INotifyDataErrorInfo.

pull/514/head
Jeremy Koritzinsky 10 years ago
parent
commit
f882e14e41
  1. 5
      src/Markup/Perspex.Markup/Data/Plugins/IPropertyAccessorPlugin.cs
  2. 51
      src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs
  3. 4
      src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs
  4. 2
      src/Markup/Perspex.Markup/Data/PropertyAccessorNode.cs
  5. 139
      tests/Perspex.Markup.UnitTests/Data/InpcPluginTests.cs
  6. 1
      tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj

5
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
/// <param name="reference">A weak reference to the object.</param>
/// <param name="propertyName">The property name.</param>
/// <param name="changed">A function to call when the property changes.</param>
/// <param name="validationChanged">A function to call when the validation status of the property changes.</param>
/// <returns>
/// An <see cref="IPropertyAccessor"/> 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<object> changed);
Action<object> changed,
Action<IEnumerable> validationChanged);
}
}

51
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
/// <param name="reference">The object.</param>
/// <param name="propertyName">The property name.</param>
/// <param name="changed">A function to call when the property changes.</param>
/// <param name="validationChanged">A function to call when the validation state of the property changes.</param>
/// <returns>
/// An <see cref="IPropertyAccessor"/> 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<object> changed)
Action<object> changed,
Action<IEnumerable> validationChanged)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(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<PropertyChangedEventArgs>
private class Accessor : IPropertyAccessor, IWeakSubscriber<PropertyChangedEventArgs>, IWeakSubscriber<DataErrorsChangedEventArgs>
{
private readonly WeakReference _reference;
private readonly PropertyInfo _property;
private readonly Action<object> _changed;
private readonly Action<IEnumerable> _validationChanged;
public Accessor(
WeakReference reference,
PropertyInfo property,
Action<object> changed)
Action<object> changed,
Action<IEnumerable> validationChanged)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(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<PropertyChangedEventArgs>(
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<DataErrorsChangedEventArgs>(
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<PropertyChangedEventArgs>(
inpc,
nameof(inpc.PropertyChanged),
this);
}
var indei = _reference.Target as INotifyDataErrorInfo;
if (indei != null)
{
WeakSubscriptionManager.Unsubscribe<DataErrorsChangedEventArgs>(
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<PropertyChangedEventArgs>.OnEvent(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName))
{
_changed(Value);
}
}
void IWeakSubscriber<DataErrorsChangedEventArgs>.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));
}
}
}
}
}

4
src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs

@ -31,6 +31,7 @@ namespace Perspex.Markup.Data.Plugins
/// <param name="reference">A weak reference to the object.</param>
/// <param name="propertyName">The property name.</param>
/// <param name="changed">A function to call when the property changes.</param>
/// <param name="validationChanged">A function to call when the validation state of the property changes.</param>
/// <returns>
/// An <see cref="IPropertyAccessor"/> 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<object> changed)
Action<object> changed,
Action<System.Collections.IEnumerable> validationChanged)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(propertyName != null);

2
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)
{

139
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<DataErrorsChangedEventArgs> 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);
}
}
}

1
tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj

@ -97,6 +97,7 @@
<Compile Include="Data\ExpressionObserverTests_SetValue.cs" />
<Compile Include="Data\ExpressionObserverTests_Task.cs" />
<Compile Include="Data\ExpressionSubjectTests.cs" />
<Compile Include="Data\InpcPluginTests.cs" />
<Compile Include="DefaultValueConverterTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UnitTestSynchronizationContext.cs" />

Loading…
Cancel
Save