diff --git a/src/Perspex.Base/DirectPropertyMetadata`1.cs b/src/Perspex.Base/DirectPropertyMetadata`1.cs new file mode 100644 index 0000000000..162fc788fc --- /dev/null +++ b/src/Perspex.Base/DirectPropertyMetadata`1.cs @@ -0,0 +1,52 @@ +// 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; + +namespace Perspex +{ + /// + /// Metadata for direct perspex properties. + /// + public class DirectPropertyMetadata : PropertyMetadata, IDirectPropertyMetadata + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The value to use when the property is set to + /// + /// The default binding mode. + public DirectPropertyMetadata( + TValue unsetValue = default(TValue), + BindingMode defaultBindingMode = BindingMode.Default) + : base(defaultBindingMode) + { + UnsetValue = unsetValue; + } + + /// + /// Gets the to use when the property is set to . + /// + public TValue UnsetValue { get; private set; } + + /// + object IDirectPropertyMetadata.UnsetValue => UnsetValue; + + /// + public override void Merge(PropertyMetadata baseMetadata, PerspexProperty property) + { + base.Merge(baseMetadata, property); + + var src = baseMetadata as DirectPropertyMetadata; + + if (src != null) + { + if (UnsetValue == null) + { + UnsetValue = src.UnsetValue; + } + } + } + } +} diff --git a/src/Perspex.Base/IDirectPropertyMetadata.cs b/src/Perspex.Base/IDirectPropertyMetadata.cs new file mode 100644 index 0000000000..72c55300ed --- /dev/null +++ b/src/Perspex.Base/IDirectPropertyMetadata.cs @@ -0,0 +1,16 @@ +// 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 +{ + /// + /// Untyped interface to + /// + public interface IDirectPropertyMetadata + { + /// + /// Gets the to use when the property is set to . + /// + object UnsetValue { get; } + } +} \ No newline at end of file diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index b886e9b08b..c06cdbe16b 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -56,6 +56,7 @@ + @@ -92,6 +93,7 @@ + diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index c3b6f321f0..16819428ca 100644 --- a/src/Perspex.Base/PerspexObject.cs +++ b/src/Perspex.Base/PerspexObject.cs @@ -268,14 +268,7 @@ namespace Perspex { Contract.Requires(property != null); - if (property.IsDirect) - { - return (T)((IDirectPropertyAccessor)GetRegistered(property)).GetValue(this); - } - else - { - return (T)GetValue((PerspexProperty)property); - } + return (T)GetValue((PerspexProperty)property); } /// @@ -315,7 +308,7 @@ namespace Perspex { var accessor = (IDirectPropertyAccessor)GetRegistered(property); LogPropertySet(property, value, priority); - accessor.SetValue(this, UnsetToDefault(value, property)); + accessor.SetValue(this, DirectUnsetToDefault(value, property)); } else { @@ -567,19 +560,6 @@ namespace Perspex } } - /// - /// Converts an unset value to the default value for a property type. - /// - /// The value. - /// The property. - /// The value. - private static object UnsetToDefault(object value, PerspexProperty property) - { - return value == PerspexProperty.UnsetValue ? - TypeUtilities.Default(property.PropertyType) : - value; - } - /// /// Creates a for a . /// @@ -626,6 +606,19 @@ namespace Perspex return result; } + /// + /// Converts an unset value to the default value for a direct property. + /// + /// The value. + /// The property. + /// The value. + private object DirectUnsetToDefault(object value, PerspexProperty property) + { + return value == PerspexProperty.UnsetValue ? + ((IDirectPropertyMetadata)property.GetMetadata(GetType())).UnsetValue : + value; + } + /// /// Gets the default value for a property. /// diff --git a/src/Perspex.Base/PerspexProperty.cs b/src/Perspex.Base/PerspexProperty.cs index 8998627849..71dd7bb0fb 100644 --- a/src/Perspex.Base/PerspexProperty.cs +++ b/src/Perspex.Base/PerspexProperty.cs @@ -342,18 +342,25 @@ namespace Perspex /// The name of the property. /// Gets the current value of the property. /// Sets the value of the property. + /// + /// The value to use when the property is set to + /// /// The default binding mode for the property. /// A public static DirectProperty RegisterDirect( string name, Func getter, Action setter = null, + TValue unsetValue = default(TValue), BindingMode defaultBindingMode = BindingMode.OneWay) where TOwner : IPerspexObject { Contract.Requires(name != null); - var metadata = new PropertyMetadata(defaultBindingMode); + var metadata = new DirectPropertyMetadata( + unsetValue: unsetValue, + defaultBindingMode: defaultBindingMode); + var result = new DirectProperty(name, getter, setter, metadata); PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result); return result; diff --git a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs index f6e405a24a..4d9482e7de 100644 --- a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs @@ -53,7 +53,8 @@ namespace Perspex.Controls.Primitives PerspexProperty.RegisterDirect( nameof(SelectedIndex), o => o.SelectedIndex, - (o, v) => o.SelectedIndex = v); + (o, v) => o.SelectedIndex = v, + unsetValue: -1); /// /// Defines the property. @@ -62,7 +63,8 @@ namespace Perspex.Controls.Primitives PerspexProperty.RegisterDirect( nameof(SelectedItem), o => o.SelectedItem, - (o, v) => o.SelectedItem = v, BindingMode.TwoWay); + (o, v) => o.SelectedItem = v, + defaultBindingMode: BindingMode.TwoWay); /// /// Defines the property. diff --git a/src/Perspex.Controls/Primitives/ToggleButton.cs b/src/Perspex.Controls/Primitives/ToggleButton.cs index c0aedf00ba..30b76aea1c 100644 --- a/src/Perspex.Controls/Primitives/ToggleButton.cs +++ b/src/Perspex.Controls/Primitives/ToggleButton.cs @@ -13,7 +13,8 @@ namespace Perspex.Controls.Primitives PerspexProperty.RegisterDirect( "IsChecked", o => o.IsChecked, - (o,v) => o.IsChecked = v, BindingMode.TwoWay); + (o,v) => o.IsChecked = v, + defaultBindingMode: BindingMode.TwoWay); private bool _isChecked; diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs index 93261fa01a..6c5b43888f 100644 --- a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs +++ b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs @@ -62,7 +62,7 @@ namespace Perspex.Base.UnitTests target.SetValue((PerspexProperty)Class1.BazProperty, PerspexProperty.UnsetValue); - Assert.Equal(0, target.Baz); + Assert.Equal(-1, target.Baz); } [Fact] @@ -160,7 +160,7 @@ namespace Perspex.Base.UnitTests } [Fact] - public void Bind_NonGeneric_Coerces_UnsetValue() + public void Bind_NonGeneric_Uses_UnsetValue() { var target = new Class1(); var source = new Subject(); @@ -171,7 +171,7 @@ namespace Perspex.Base.UnitTests source.OnNext(6); Assert.Equal(6, target.Baz); source.OnNext(PerspexProperty.UnsetValue); - Assert.Equal(0, target.Baz); + Assert.Equal(-1, target.Baz); } [Fact] @@ -282,6 +282,17 @@ namespace Perspex.Base.UnitTests Assert.Equal("newvalue", target.Foo); } + + [Fact] + public void UnsetValue_Is_Used_On_AddOwnered_Property() + { + var target = new Class2(); + + target.SetValue((PerspexProperty)Class1.FooProperty, PerspexProperty.UnsetValue); + + Assert.Equal("unset", target.Foo); + } + [Fact] public void Bind_Binds_AddOwnered_Property_Value() { @@ -342,13 +353,21 @@ namespace Perspex.Base.UnitTests private class Class1 : PerspexObject { public static readonly DirectProperty FooProperty = - PerspexProperty.RegisterDirect("Foo", o => o.Foo, (o, v) => o.Foo = v); + PerspexProperty.RegisterDirect( + "Foo", + o => o.Foo, + (o, v) => o.Foo = v, + unsetValue: "unset"); public static readonly DirectProperty BarProperty = PerspexProperty.RegisterDirect("Bar", o => o.Bar); public static readonly DirectProperty BazProperty = - PerspexProperty.RegisterDirect("Bar", o => o.Baz, (o,v) => o.Baz = v); + PerspexProperty.RegisterDirect( + "Bar", + o => o.Baz, + (o,v) => o.Baz = v, + unsetValue: -1); private string _foo = "initial"; private readonly string _bar = "bar";