Browse Source

Change the way PriorityValue notifies.

Directly call a Changed method on the PriorityValue owner rather than using an observable.
pull/494/merge
Steven Kirk 11 years ago
parent
commit
c984a504a2
  1. 19
      src/Perspex.Base/IPriorityValueOwner.cs
  2. 1
      src/Perspex.Base/Perspex.Base.csproj
  3. 56
      src/Perspex.Base/PerspexObject.cs
  4. 36
      src/Perspex.Base/PriorityValue.cs
  5. 3
      src/Perspex.Base/Properties/AssemblyInfo.cs
  6. 4
      tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj
  7. 59
      tests/Perspex.Base.UnitTests/PriorityValueTests.cs
  8. 1
      tests/Perspex.Base.UnitTests/packages.config

19
src/Perspex.Base/IPriorityValueOwner.cs

@ -0,0 +1,19 @@
// 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.
namespace Perspex
{
/// <summary>
/// An owner of a <see cref="PriorityValue"/>.
/// </summary>
internal interface IPriorityValueOwner
{
/// <summary>
/// Called when a <see cref="PriorityValue"/>'s value changes.
/// </summary>
/// <param name="sender">The source of the change.</param>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
void Changed(PriorityValue sender, object oldValue, object newValue);
}
}

1
src/Perspex.Base/Perspex.Base.csproj

@ -54,6 +54,7 @@
<Compile Include="Diagnostics\IPerspexObjectDebug.cs" />
<Compile Include="Diagnostics\PerspexObjectExtensions.cs" />
<Compile Include="AttachedProperty.cs" />
<Compile Include="IPriorityValueOwner.cs" />
<Compile Include="IStyledPropertyAccessor.cs" />
<Compile Include="IDirectPropertyAccessor.cs" />
<Compile Include="DirectProperty.cs" />

56
src/Perspex.Base/PerspexObject.cs

@ -21,7 +21,7 @@ namespace Perspex
/// <remarks>
/// This class is analogous to DependencyObject in WPF.
/// </remarks>
public class PerspexObject : IPerspexObject, IPerspexObjectDebug, INotifyPropertyChanged
public class PerspexObject : IPerspexObject, IPerspexObjectDebug, INotifyPropertyChanged, IPriorityValueOwner
{
/// <summary>
/// Maintains a list of direct property binding subscriptions so that the binding source
@ -477,6 +477,34 @@ namespace Perspex
}
}
/// <inheritdoc/>
void IPriorityValueOwner.Changed(PriorityValue sender, object oldValue, object newValue)
{
var property = sender.Property;
var priority = (BindingPriority)sender.ValuePriority;
oldValue = (oldValue == PerspexProperty.UnsetValue) ?
GetDefaultValue(property) :
oldValue;
newValue = (newValue == PerspexProperty.UnsetValue) ?
GetDefaultValue(property) :
newValue;
if (!Equals(oldValue, newValue))
{
RaisePropertyChanged(property, oldValue, newValue, priority);
Logger.Verbose(
LogArea.Property,
this,
"{Property} changed from {$Old} to {$Value} with priority {Priority}",
property,
oldValue,
newValue,
priority);
}
}
/// <inheritdoc/>
Delegate[] IPerspexObjectDebug.GetPropertyChangedSubscribers()
{
@ -604,34 +632,10 @@ namespace Perspex
PriorityValue result = new PriorityValue(
this,
property.Name,
property,
property.PropertyType,
validate2);
result.Changed.Subscribe(x =>
{
object oldValue = (x.Item1 == PerspexProperty.UnsetValue) ?
GetDefaultValue(property) :
x.Item1;
object newValue = (x.Item2 == PerspexProperty.UnsetValue) ?
GetDefaultValue(property) :
x.Item2;
if (!Equals(oldValue, newValue))
{
RaisePropertyChanged(property, oldValue, newValue, (BindingPriority)result.ValuePriority);
Logger.Verbose(
LogArea.Property,
this,
"{Property} changed from {$Old} to {$Value} with priority {Priority}",
property,
oldValue,
newValue,
(BindingPriority)result.ValuePriority);
}
});
return result;
}

36
src/Perspex.Base/PriorityValue.cs

@ -20,20 +20,16 @@ namespace Perspex
/// represent higher priorites. The current <see cref="Value"/> is selected from the highest
/// priority binding that doesn't return <see cref="PerspexProperty.UnsetValue"/>. Where there
/// are multiple bindings registered with the same priority, the most recently added binding
/// has a higher priority. Each time the value changes, the <see cref="Changed"/> observable is
/// fired with the old and new values.
/// has a higher priority. Each time the value changes, the
/// <see cref="IPriorityValueOwner.Changed(PerspexProperty, object, object)"/> method on the
/// owner object is fired with the old and new values.
/// </remarks>
internal class PriorityValue
{
/// <summary>
/// The owner of the object.
/// </summary>
private readonly PerspexObject _owner;
/// <summary>
/// The name of the property.
/// </summary>
private readonly string _name;
private readonly IPriorityValueOwner _owner;
/// <summary>
/// The value type.
@ -45,11 +41,6 @@ namespace Perspex
/// </summary>
private readonly Dictionary<int, PriorityLevel> _levels = new Dictionary<int, PriorityLevel>();
/// <summary>
/// The changed observable.
/// </summary>
private readonly Subject<Tuple<object, object>> _changed = new Subject<Tuple<object, object>>();
/// <summary>
/// The current value.
/// </summary>
@ -64,17 +55,17 @@ namespace Perspex
/// Initializes a new instance of the <see cref="PriorityValue"/> class.
/// </summary>
/// <param name="owner">The owner of the object.</param>
/// <param name="name">The name of the property.</param>
/// <param name="property">The property that the value represents.</param>
/// <param name="valueType">The value type.</param>
/// <param name="validate">An optional validation function.</param>
public PriorityValue(
PerspexObject owner,
string name,
IPriorityValueOwner owner,
PerspexProperty property,
Type valueType,
Func<object, object> validate = null)
{
_owner = owner;
_name = name;
Property = property;
_valueType = valueType;
_value = PerspexProperty.UnsetValue;
ValuePriority = int.MaxValue;
@ -82,12 +73,9 @@ namespace Perspex
}
/// <summary>
/// Fired whenever the current <see cref="Value"/> changes.
/// Gets the property that the value represents.
/// </summary>
/// <remarks>
/// The old and new values may be the same, this class does not check for distinct values.
/// </remarks>
public IObservable<Tuple<object, object>> Changed => _changed;
public PerspexProperty Property { get; }
/// <summary>
/// Gets the current value.
@ -236,7 +224,7 @@ namespace Perspex
ValuePriority = priority;
_value = castValue;
_changed.OnNext(Tuple.Create(old, _value));
_owner?.Changed(this, old, _value);
}
else
{
@ -244,7 +232,7 @@ namespace Perspex
LogArea.Property,
_owner,
"Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})",
_name,
Property.Name,
_valueType,
value,
value.GetType());

3
src/Perspex.Base/Properties/AssemblyInfo.cs

@ -5,4 +5,5 @@ using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyTitle("Perspex.Base")]
[assembly: InternalsVisibleTo("Perspex.Base.UnitTests")]
[assembly: InternalsVisibleTo("Perspex.Base.UnitTests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

4
tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj

@ -44,6 +44,10 @@
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="Moq, Version=4.2.1510.2205, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\..\packages\Moq.4.2.1510.2205\lib\net40\Moq.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll</HintPath>

59
tests/Perspex.Base.UnitTests/PriorityValueTests.cs

@ -5,16 +5,23 @@ using System;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Moq;
using Xunit;
namespace Perspex.Base.UnitTests
{
public class PriorityValueTests
{
private static readonly PerspexProperty TestProperty =
new StyledProperty<string>(
"Test",
typeof(PriorityValueTests),
new StyledPropertyMetadata<string>());
[Fact]
public void Initial_Value_Should_Be_UnsetValue()
{
var target = new PriorityValue(null, "Test", typeof(string));
var target = new PriorityValue(null, TestProperty, typeof(string));
Assert.Same(PerspexProperty.UnsetValue, target.Value);
}
@ -22,7 +29,7 @@ namespace Perspex.Base.UnitTests
[Fact]
public void First_Binding_Sets_Value()
{
var target = new PriorityValue(null, "Test", typeof(string));
var target = new PriorityValue(null, TestProperty, typeof(string));
target.Add(Single("foo"), 0);
@ -32,7 +39,7 @@ namespace Perspex.Base.UnitTests
[Fact]
public void Changing_Binding_Should_Set_Value()
{
var target = new PriorityValue(null, "Test", typeof(string));
var target = new PriorityValue(null, TestProperty, typeof(string));
var subject = new BehaviorSubject<string>("foo");
target.Add(subject, 0);
@ -44,7 +51,7 @@ namespace Perspex.Base.UnitTests
[Fact]
public void Setting_Direct_Value_Should_Override_Binding()
{
var target = new PriorityValue(null, "Test", typeof(string));
var target = new PriorityValue(null, TestProperty, typeof(string));
target.Add(Single("foo"), 0);
target.SetValue("bar", 0);
@ -55,7 +62,7 @@ namespace Perspex.Base.UnitTests
[Fact]
public void Binding_Firing_Should_Override_Direct_Value()
{
var target = new PriorityValue(null, "Test", typeof(string));
var target = new PriorityValue(null, TestProperty, typeof(string));
var source = new BehaviorSubject<object>("initial");
target.Add(source, 0);
@ -69,7 +76,7 @@ namespace Perspex.Base.UnitTests
[Fact]
public void Earlier_Binding_Firing_Should_Not_Override_Later()
{
var target = new PriorityValue(null, "Test", typeof(string));
var target = new PriorityValue(null, TestProperty, typeof(string));
var nonActive = new BehaviorSubject<object>("na");
var source = new BehaviorSubject<object>("initial");
@ -85,7 +92,7 @@ namespace Perspex.Base.UnitTests
[Fact]
public void Binding_Completing_Should_Revert_To_Direct_Value()
{
var target = new PriorityValue(null, "Test", typeof(string));
var target = new PriorityValue(null, TestProperty, typeof(string));
var source = new BehaviorSubject<object>("initial");
target.Add(source, 0);
@ -101,7 +108,7 @@ namespace Perspex.Base.UnitTests
[Fact]
public void Binding_With_Lower_Priority_Has_Precedence()
{
var target = new PriorityValue(null, "Test", typeof(string));
var target = new PriorityValue(null, TestProperty, typeof(string));
target.Add(Single("foo"), 1);
target.Add(Single("bar"), 0);
@ -113,7 +120,7 @@ namespace Perspex.Base.UnitTests
[Fact]
public void Later_Binding_With_Same_Priority_Should_Take_Precedence()
{
var target = new PriorityValue(null, "Test", typeof(string));
var target = new PriorityValue(null, TestProperty, typeof(string));
target.Add(Single("foo"), 1);
target.Add(Single("bar"), 0);
@ -126,7 +133,7 @@ namespace Perspex.Base.UnitTests
[Fact]
public void Changing_Binding_With_Lower_Priority_Should_Set_Not_Value()
{
var target = new PriorityValue(null, "Test", typeof(string));
var target = new PriorityValue(null, TestProperty, typeof(string));
var subject = new BehaviorSubject<string>("bar");
target.Add(Single("foo"), 0);
@ -139,7 +146,7 @@ namespace Perspex.Base.UnitTests
[Fact]
public void UnsetValue_Should_Fall_Back_To_Next_Binding()
{
var target = new PriorityValue(null, "Test", typeof(string));
var target = new PriorityValue(null, TestProperty, typeof(string));
var subject = new BehaviorSubject<object>("bar");
target.Add(subject, 0);
@ -155,33 +162,31 @@ namespace Perspex.Base.UnitTests
[Fact]
public void Adding_Value_Should_Call_OnNext()
{
var target = new PriorityValue(null, "Test", typeof(string));
bool called = false;
var owner = new Mock<IPriorityValueOwner>();
var target = new PriorityValue(owner.Object, TestProperty, typeof(string));
target.Changed.Subscribe(value => called = value.Item1 == PerspexProperty.UnsetValue && (string)value.Item2 == "foo");
target.Add(Single("foo"), 0);
Assert.True(called);
owner.Verify(x => x.Changed(target, PerspexProperty.UnsetValue, "foo"));
}
[Fact]
public void Changing_Value_Should_Call_OnNext()
{
var target = new PriorityValue(null, "Test", typeof(string));
var owner = new Mock<IPriorityValueOwner>();
var target = new PriorityValue(owner.Object, TestProperty, typeof(string));
var subject = new BehaviorSubject<object>("foo");
bool called = false;
target.Add(subject, 0);
target.Changed.Subscribe(value => called = (string)value.Item1 == "foo" && (string)value.Item2 == "bar");
subject.OnNext("bar");
Assert.True(called);
owner.Verify(x => x.Changed(target, "foo", "bar"));
}
[Fact]
public void Disposing_A_Binding_Should_Revert_To_Next_Value()
{
var target = new PriorityValue(null, "Test", typeof(string));
var target = new PriorityValue(null, TestProperty, typeof(string));
target.Add(Single("foo"), 0);
var disposable = target.Add(Single("bar"), 0);
@ -194,7 +199,7 @@ namespace Perspex.Base.UnitTests
[Fact]
public void Disposing_A_Binding_Should_Remove_BindingEntry()
{
var target = new PriorityValue(null, "Test", typeof(string));
var target = new PriorityValue(null, TestProperty, typeof(string));
target.Add(Single("foo"), 0);
var disposable = target.Add(Single("bar"), 0);
@ -207,7 +212,7 @@ namespace Perspex.Base.UnitTests
[Fact]
public void Completing_A_Binding_Should_Revert_To_Previous_Binding()
{
var target = new PriorityValue(null, "Test", typeof(string));
var target = new PriorityValue(null, TestProperty, typeof(string));
var source = new BehaviorSubject<object>("bar");
target.Add(Single("foo"), 0);
@ -221,7 +226,7 @@ namespace Perspex.Base.UnitTests
[Fact]
public void Completing_A_Binding_Should_Revert_To_Lower_Priority()
{
var target = new PriorityValue(null, "Test", typeof(string));
var target = new PriorityValue(null, TestProperty, typeof(string));
var source = new BehaviorSubject<object>("bar");
target.Add(Single("foo"), 1);
@ -235,7 +240,7 @@ namespace Perspex.Base.UnitTests
[Fact]
public void Completing_A_Binding_Should_Remove_BindingEntry()
{
var target = new PriorityValue(null, "Test", typeof(string));
var target = new PriorityValue(null, TestProperty, typeof(string));
var subject = new BehaviorSubject<object>("bar");
target.Add(Single("foo"), 0);
@ -249,7 +254,7 @@ namespace Perspex.Base.UnitTests
[Fact]
public void Direct_Value_Should_Be_Coerced()
{
var target = new PriorityValue(null, "Test", typeof(int), x => Math.Min((int)x, 10));
var target = new PriorityValue(null, TestProperty, typeof(int), x => Math.Min((int)x, 10));
target.SetValue(5, 0);
Assert.Equal(5, target.Value);
@ -260,7 +265,7 @@ namespace Perspex.Base.UnitTests
[Fact]
public void Bound_Value_Should_Be_Coerced()
{
var target = new PriorityValue(null, "Test", typeof(int), x => Math.Min((int)x, 10));
var target = new PriorityValue(null, TestProperty, typeof(int), x => Math.Min((int)x, 10));
var source = new Subject<object>();
target.Add(source, 0);
@ -274,7 +279,7 @@ namespace Perspex.Base.UnitTests
public void Revalidate_Should_ReCoerce_Value()
{
var max = 10;
var target = new PriorityValue(null, "Test", typeof(int), x => Math.Min((int)x, max));
var target = new PriorityValue(null, TestProperty, typeof(int), x => Math.Min((int)x, max));
var source = new Subject<object>();
target.Add(source, 0);

1
tests/Perspex.Base.UnitTests/packages.config

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Moq" version="4.2.1510.2205" targetFramework="net45" />
<package id="Rx-Core" version="2.2.5" targetFramework="net45" />
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" />
<package id="Rx-Linq" version="2.2.5" targetFramework="net45" />

Loading…
Cancel
Save