Browse Source

More WIP refactoring properties.

Compiles now but lots of tests failing.
pull/387/merge
Steven Kirk 10 years ago
parent
commit
d25e057ccc
  1. 2
      src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs
  2. 28
      src/Perspex.Base/AttachedProperty.cs
  3. 2
      src/Perspex.Base/DirectProperty.cs
  4. 2
      src/Perspex.Base/Perspex.Base.csproj
  5. 34
      src/Perspex.Base/PerspexObject.cs
  6. 2
      src/Perspex.Base/PerspexObjectExtensions.cs
  7. 196
      src/Perspex.Base/PerspexProperty.cs
  8. 14
      src/Perspex.Base/PerspexProperty`1.cs
  9. 66
      src/Perspex.Base/PropertyMetadata.cs
  10. 40
      src/Perspex.Base/StyledProperty.cs
  11. 151
      src/Perspex.Base/StyledPropertyBase.cs
  12. 63
      src/Perspex.Base/StyledPropertyMetadata.cs
  13. 8
      src/Perspex.Controls/Border.cs
  14. 10
      src/Perspex.Controls/Button.cs
  15. 5
      src/Perspex.Controls/Carousel.cs
  16. 9
      src/Perspex.Controls/ContentControl.cs
  17. 4
      src/Perspex.Controls/Control.cs
  18. 5
      src/Perspex.Controls/Decorator.cs
  19. 4
      src/Perspex.Controls/HotkeyManager.cs
  20. 8
      src/Perspex.Controls/ItemsControl.cs
  21. 2
      src/Perspex.Controls/ListBoxItem.cs
  22. 10
      src/Perspex.Controls/MenuItem.cs
  23. 9
      src/Perspex.Controls/Presenters/ScrollContentPresenter.cs
  24. 2
      src/Perspex.Controls/Primitives/HeaderedContentControl.cs
  25. 2
      src/Perspex.Controls/Primitives/HeaderedSelectingControl.cs
  26. 1
      src/Perspex.Controls/Primitives/PopupRoot.cs
  27. 6
      src/Perspex.Controls/Primitives/RangeBase.cs
  28. 6
      src/Perspex.Controls/Primitives/ScrollBar.cs
  29. 8
      src/Perspex.Controls/Primitives/SelectingItemsControl.cs
  30. 17
      src/Perspex.Controls/Primitives/TemplatedControl.cs
  31. 24
      src/Perspex.Controls/ScrollViewer.cs
  32. 21
      src/Perspex.Controls/TextBlock.cs
  33. 21
      src/Perspex.Controls/TextBox.cs
  34. 2
      src/Perspex.HtmlRenderer/HtmlControl.cs
  35. 14
      src/Perspex.Input/InputElement.cs
  36. 6
      src/Perspex.Input/KeyboardNavigation.cs
  37. 16
      src/Perspex.SceneGraph/Visual.cs
  38. 2
      src/Perspex.Styling/Styling/Setter.cs
  39. 26
      tests/Perspex.Base.UnitTests/AttachedPropertyTests.cs
  40. 87
      tests/Perspex.Base.UnitTests/DirectPropertyTests.cs
  41. 3
      tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj
  42. 4
      tests/Perspex.Base.UnitTests/PerspexPropertyRegistryTests.cs
  43. 274
      tests/Perspex.Base.UnitTests/PerspexPropertyTests.cs
  44. 48
      tests/Perspex.Base.UnitTests/StyledPropertyTests.cs

2
src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs

@ -90,7 +90,7 @@ namespace Perspex.Markup.Xaml.Data
internal void Bind(IPerspexObject target, PerspexProperty property, ISubject<object> subject)
{
var mode = Mode == BindingMode.Default ?
property.DefaultBindingMode : Mode;
property.GetMetadata(target.GetType()).DefaultBindingMode : Mode;
switch (mode)
{

28
src/Perspex.Base/AttachedProperty.cs

@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Perspex.Data;
namespace Perspex
{
@ -17,25 +16,30 @@ namespace Perspex
/// </summary>
/// <param name="name">The name of the property.</param>
/// <param name="ownerType">The class that is registering the property.</param>
/// <param name="defaultValue">The default value of the property.</param>
/// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="defaultBindingMode">The default binding mode for the property.</param>
/// <param name="validate">A validation function.</param>
/// <param name="metadata">The property metadata.</param>
public AttachedProperty(
string name,
Type ownerType,
TValue defaultValue = default(TValue),
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.Default,
Func<IPerspexObject, TValue, TValue> validate = null)
: base(name, ownerType, defaultValue, inherits, defaultBindingMode, validate)
bool inherits,
StyledPropertyMetadata metadata)
: base(name, ownerType, inherits, metadata)
{
}
/// <inheritdoc/>
public override string FullName => OwnerType + "." + Name;
/// <inheritdoc/>
public override bool IsAttached => true;
/// <summary>
/// Attaches the property as a non-attached property on the specified type.
/// </summary>
/// <typeparam name="TOwner">The owner type.</typeparam>
/// <returns>The property.</returns>
public StyledProperty<TValue> AddOwner<TOwner>()
{
var result = new StyledProperty<TValue>(this, typeof(TOwner));
PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
return result;
}
}
}

2
src/Perspex.Base/DirectProperty.cs

@ -28,7 +28,7 @@ namespace Perspex
string name,
Func<TOwner, TValue> getter,
Action<TOwner, TValue> setter = null)
: base(name, typeof(TOwner))
: base(name, typeof(TOwner), new PropertyMetadata())
{
Contract.Requires<ArgumentNullException>(getter != null);

2
src/Perspex.Base/Perspex.Base.csproj

@ -58,6 +58,7 @@
<Compile Include="PerspexLocator.cs" />
<Compile Include="Metadata\XmlnsDefinitionAttribute.cs" />
<Compile Include="PerspexObjectExtensions.cs" />
<Compile Include="PropertyMetadata.cs" />
<Compile Include="StyledProperty.cs" />
<Compile Include="StyledPropertyBase.cs" />
<Compile Include="PerspexPropertyRegistry.cs" />
@ -85,6 +86,7 @@
<Compile Include="Reactive\AnonymousSubject`1.cs" />
<Compile Include="Reactive\AnonymousSubject`2.cs" />
<Compile Include="Reactive\PerspexObservable.cs" />
<Compile Include="StyledPropertyMetadata.cs" />
<Compile Include="Threading\Dispatcher.cs" />
<Compile Include="Threading\DispatcherPriority.cs" />
<Compile Include="Threading\DispatcherTimer.cs" />

34
src/Perspex.Base/PerspexObject.cs

@ -57,21 +57,21 @@ namespace Perspex
new PropertyEnricher("Id", GetHashCode()),
});
////foreach (var property in PerspexPropertyRegistry.Instance.GetRegistered(this))
////{
//// object value = property.IsDirect ?
//// ((IDirectPropertyAccessor)property).GetValue(this) :
//// property.GetDefaultValue(GetType());
//// var e = new PerspexPropertyChangedEventArgs(
//// this,
//// property,
//// PerspexProperty.UnsetValue,
//// value,
//// BindingPriority.Unset);
//// property.NotifyInitialized(e);
////}
foreach (var property in PerspexPropertyRegistry.Instance.GetRegistered(this))
{
object value = property.IsDirect ?
((IDirectPropertyAccessor)property).GetValue(this) :
((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
var e = new PerspexPropertyChangedEventArgs(
this,
property,
PerspexProperty.UnsetValue,
value,
BindingPriority.Unset);
property.NotifyInitialized(e);
}
}
/// <summary>
@ -162,8 +162,10 @@ namespace Perspex
set
{
var metadata = binding.Property.GetMetadata(GetType());
var mode = (binding.Mode == BindingMode.Default) ?
binding.Property.DefaultBindingMode :
metadata.DefaultBindingMode :
binding.Mode;
var sourceBinding = value as IndexerDescriptor;

2
src/Perspex.Base/PerspexObjectExtensions.cs

@ -184,7 +184,7 @@ namespace Perspex
if (mode == BindingMode.Default)
{
mode = property.DefaultBindingMode;
mode = property.GetMetadata(o.GetType()).DefaultBindingMode;
}
return o.Bind(

196
src/Perspex.Base/PerspexProperty.cs

@ -2,14 +2,16 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Reactive.Subjects;
using System.Reflection;
using Perspex.Data;
using Perspex.Utilities;
namespace Perspex
{
/// <summary>
/// Base class for perspex property metadata.
/// Base class for perspex properties.
/// </summary>
public class PerspexProperty : IEquatable<PerspexProperty>
{
@ -18,25 +20,12 @@ namespace Perspex
/// </summary>
public static readonly object UnsetValue = new Unset();
/// <summary>
/// Gets the next ID that will be allocated to a property.
/// </summary>
private static int s_nextId = 1;
/// <summary>
/// Observable fired when this property changes on any <see cref="PerspexObject"/>.
/// </summary>
private readonly int _id;
private readonly Subject<PerspexPropertyChangedEventArgs> _initialized;
/// <summary>
/// Observable fired when this property changes on any <see cref="PerspexObject"/>.
/// </summary>
private readonly Subject<PerspexPropertyChangedEventArgs> _changed;
/// <summary>
/// Gets the ID of the property.
/// </summary>
private readonly int _id;
private readonly PropertyMetadata _defaultMetadata;
private readonly Dictionary<Type, PropertyMetadata> _metadata;
/// <summary>
/// Initializes a new instance of the <see cref="PerspexProperty"/> class.
@ -44,22 +33,17 @@ namespace Perspex
/// <param name="name">The name of the property.</param>
/// <param name="valueType">The type of the property's value.</param>
/// <param name="ownerType">The type of the class that registers the property.</param>
/// <param name="defaultBindingMode">The default binding mode for the property.</param>
/// <param name="notifying">
/// A method that gets called before and after the property starts being notified on an
/// object; the bool argument will be true before and false afterwards. This callback is
/// intended to support IsDataContextChanging.
/// </param>
/// <param name="metadata">The property metadata.</param>
protected PerspexProperty(
string name,
Type valueType,
Type ownerType,
BindingMode defaultBindingMode = BindingMode.Default,
Action<PerspexObject, bool> notifying = null)
PropertyMetadata metadata)
{
Contract.Requires<ArgumentNullException>(name != null);
Contract.Requires<ArgumentNullException>(valueType != null);
Contract.Requires<ArgumentNullException>(ownerType != null);
Contract.Requires<ArgumentNullException>(metadata != null);
if (name.Contains("."))
{
@ -68,13 +52,15 @@ namespace Perspex
_initialized = new Subject<PerspexPropertyChangedEventArgs>();
_changed = new Subject<PerspexPropertyChangedEventArgs>();
_metadata = new Dictionary<Type, PropertyMetadata>();
Name = name;
PropertyType = valueType;
OwnerType = ownerType;
DefaultBindingMode = defaultBindingMode;
Notifying = notifying;
_id = s_nextId++;
_metadata.Add(ownerType, metadata);
_defaultMetadata = metadata;
}
/// <summary>
@ -87,19 +73,13 @@ namespace Perspex
Contract.Requires<ArgumentNullException>(source != null);
Contract.Requires<ArgumentNullException>(ownerType != null);
if (source.IsDirect)
{
throw new InvalidOperationException(
"This method cannot be called on direct PerspexProperties.");
}
_initialized = source._initialized;
_changed = source._changed;
_metadata = source._metadata;
Name = source.Name;
PropertyType = source.PropertyType;
OwnerType = ownerType;
DefaultBindingMode = source.DefaultBindingMode;
Notifying = Notifying;
_id = source._id;
}
@ -109,12 +89,6 @@ namespace Perspex
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the full name of the property, wich includes the owner type in the case of
/// attached properties.
/// </summary>
public virtual string FullName => Name;
/// <summary>
/// Gets the type of the property's value.
/// </summary>
@ -130,11 +104,6 @@ namespace Perspex
/// </summary>
public virtual bool Inherits => false;
/// <summary>
/// Gets the default binding mode for the property.
/// </summary>
public BindingMode DefaultBindingMode { get; }
/// <summary>
/// Gets a value indicating whether this is an attached property.
/// </summary>
@ -272,21 +241,18 @@ namespace Perspex
BindingMode defaultBindingMode = BindingMode.OneWay,
Func<TOwner, TValue, TValue> validate = null,
Action<IPerspexObject, bool> notifying = null)
where TOwner : IPerspexObject
where TOwner : IPerspexObject
{
Contract.Requires<ArgumentNullException>(name != null);
var result = new StyledProperty<TValue>(
name,
typeof(TOwner),
var metadata = new StyledPropertyMetadata(
defaultValue,
inherits,
defaultBindingMode,
Cast(validate),
notifying);
validate: Cast(validate),
defaultBindingMode: defaultBindingMode,
notifyingCallback: notifying);
var result = new StyledProperty<TValue>(name, typeof(TOwner), inherits, metadata);
PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
return result;
}
@ -312,16 +278,13 @@ namespace Perspex
{
Contract.Requires<ArgumentNullException>(name != null);
var result = new AttachedProperty<TValue>(
name,
typeof(TOwner),
var metadata = new StyledPropertyMetadata(
defaultValue,
inherits,
defaultBindingMode,
Cast(validate));
validate: Cast(validate),
defaultBindingMode: defaultBindingMode);
var result = new AttachedProperty<TValue>(name, typeof(TOwner), inherits, metadata);
PerspexPropertyRegistry.Instance.Register(typeof(THost), result);
return result;
}
@ -337,7 +300,7 @@ namespace Perspex
/// <param name="defaultBindingMode">The default binding mode for the property.</param>
/// <param name="validate">A validation function.</param>
/// <returns>A <see cref="PerspexProperty{TValue}"/></returns>
public static PerspexProperty<TValue> RegisterAttached<THost, TValue>(
public static AttachedProperty<TValue> RegisterAttached<THost, TValue>(
string name,
Type ownerType,
TValue defaultValue = default(TValue),
@ -348,16 +311,13 @@ namespace Perspex
{
Contract.Requires<ArgumentNullException>(name != null);
var result = new AttachedProperty<TValue>(
name,
ownerType,
var metadata = new StyledPropertyMetadata(
defaultValue,
inherits,
defaultBindingMode,
Cast(validate));
validate: Cast(validate),
defaultBindingMode: defaultBindingMode);
var result = new AttachedProperty<TValue>(name, ownerType, inherits, metadata);
PerspexPropertyRegistry.Instance.Register(typeof(THost), result);
return result;
}
@ -383,6 +343,22 @@ namespace Perspex
return result;
}
/// <summary>
/// Returns a binding accessor that can be passed to <see cref="PerspexObject"/>'s []
/// operator to initiate a binding.
/// </summary>
/// <returns>A <see cref="IndexerDescriptor"/>.</returns>
/// <remarks>
/// The ! and ~ operators are short forms of this.
/// </remarks>
public IndexerDescriptor Bind()
{
return new IndexerDescriptor
{
Property = this,
};
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
@ -403,19 +379,56 @@ namespace Perspex
}
/// <summary>
/// Returns a binding accessor that can be passed to <see cref="PerspexObject"/>'s []
/// operator to initiate a binding.
/// Gets the property metadata for the specified type.
/// </summary>
/// <returns>A <see cref="IndexerDescriptor"/>.</returns>
/// <remarks>
/// The ! and ~ operators are short forms of this.
/// </remarks>
public IndexerDescriptor Bind()
/// <typeparam name="T">The type.</typeparam>
/// <returns>
/// The property metadata.
/// </returns>
public PropertyMetadata GetMetadata<T>() where T : IPerspexObject
{
return new IndexerDescriptor
var type = typeof(T);
while (type != null)
{
Property = this,
};
PropertyMetadata result;
if (_metadata.TryGetValue(type, out result))
{
return result;
}
type = type.GetTypeInfo().BaseType;
}
return _defaultMetadata;
}
/// <summary>
/// Gets the property metadata for the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// The property metadata.
/// </returns>
public PropertyMetadata GetMetadata(Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
while (type != null)
{
PropertyMetadata result;
if (_metadata.TryGetValue(type, out result))
{
return result;
}
type = type.GetTypeInfo().BaseType;
}
return _defaultMetadata;
}
/// <summary>
@ -463,7 +476,7 @@ namespace Perspex
/// <typeparam name="TValue">The property value type.</typeparam>
/// <param name="f">The typed function.</param>
/// <returns>The untyped function.</returns>
protected static Func<IPerspexObject, TValue, TValue> Cast<TOwner, TValue>(Func<TOwner, TValue, TValue> f)
protected static Func<IPerspexObject, object, object> Cast<TOwner, TValue>(Func<TOwner, TValue, TValue> f)
where TOwner : IPerspexObject
{
if (f == null)
@ -472,10 +485,31 @@ namespace Perspex
}
else
{
return (o, v) => f((TOwner)o, v);
return (o, v) => f((TOwner)o, (TValue)v);
}
}
/// <summary>
/// Overrides the metadata for the property on the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="metadata">The metadata.</param>
protected void OverrideMetadata(Type type, PropertyMetadata metadata)
{
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(metadata != null);
if (_metadata.ContainsKey(type))
{
throw new InvalidOperationException(
$"Metadata is already set for {Name} on {type}.");
}
var baseMetadata = GetMetadata(type);
metadata.Merge(baseMetadata, this);
_metadata.Add(type, metadata);
}
/// <summary>
/// Class representing the <see cref="UnsetValue"/>.
/// </summary>

14
src/Perspex.Base/PerspexProperty`1.cs

@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Perspex.Data;
namespace Perspex
{
@ -17,23 +16,16 @@ namespace Perspex
/// </summary>
/// <param name="name">The name of the property.</param>
/// <param name="ownerType">The type of the class that registers the property.</param>
/// <param name="defaultBindingMode">The default binding mode for the property.</param>
/// <param name="notifying">
/// A method that gets called before and after the property starts being notified on an
/// object; the bool argument will be true before and false afterwards. This callback is
/// intended to support IsDataContextChanging.
/// </param>
/// <param name="metadata">The property metadata.</param>
protected PerspexProperty(
string name,
Type ownerType,
BindingMode defaultBindingMode = BindingMode.Default,
Action<PerspexObject, bool> notifying = null)
PropertyMetadata metadata)
: base(
name,
typeof(TValue),
ownerType,
defaultBindingMode,
notifying)
metadata)
{
}

66
src/Perspex.Base/PropertyMetadata.cs

@ -0,0 +1,66 @@
// 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 System;
using Perspex.Data;
namespace Perspex
{
/// <summary>
/// Base class for perspex property metadata.
/// </summary>
public class PropertyMetadata
{
/// <summary>
/// Initializes a new instance of the <see cref="PropertyMetadata"/> class.
/// </summary>
/// <param name="defaultBindingMode">The default binding mode.</param>
/// <param name="notifyingCallback">The property notifying callback.</param>
public PropertyMetadata(
BindingMode defaultBindingMode = BindingMode.Default,
Action<PerspexObject, bool> notifyingCallback = null)
{
DefaultBindingMode = defaultBindingMode;
NotifyingCallback = notifyingCallback;
}
/// <summary>
/// Gets the default binding mode for the property.
/// </summary>
public BindingMode DefaultBindingMode { get; private set; }
/// <summary>
/// Gets a method that gets called before and after the property starts being notified on an
/// object.
/// </summary>
/// <remarks>
/// When a property changes, change notifications are sent to all property subscribers;
/// for example via the <see cref="PerspexProperty.Changed"/> observable and and the
/// <see cref="PerspexObject.PropertyChanged"/> event. If this callback is set for a property,
/// then it will be called before and after these notifications take place. The bool argument
/// will be true before the property change notifications are sent and false afterwards. This
/// callback is intended to support Control.IsDataContextChanging.
/// </remarks>
public Action<PerspexObject, bool> NotifyingCallback { get; private set; }
/// <summary>
/// Merges the metadata with the base metadata.
/// </summary>
/// <param name="baseMetadata">The base metadata to merge.</param>
/// <param name="property">The property to which the metadata is being applied.</param>
public virtual void Merge(
PropertyMetadata baseMetadata,
PerspexProperty property)
{
if (DefaultBindingMode == BindingMode.Default)
{
DefaultBindingMode = baseMetadata.DefaultBindingMode;
}
if (NotifyingCallback == null)
{
NotifyingCallback = baseMetadata.NotifyingCallback;
}
}
}
}

40
src/Perspex.Base/StyledProperty.cs

@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Perspex.Data;
namespace Perspex
{
@ -16,25 +15,36 @@ namespace Perspex
/// </summary>
/// <param name="name">The name of the property.</param>
/// <param name="ownerType">The type of the class that registers the property.</param>
/// <param name="defaultValue">The default value of the property.</param>
/// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="defaultBindingMode">The default binding mode for the property.</param>
/// <param name="validate">A validation function.</param>
/// <param name="notifying">
/// A method that gets called before and after the property starts being notified on an
/// object; the bool argument will be true before and false afterwards. This callback is
/// intended to support IsDataContextChanging.
/// </param>
/// <param name="metadata">The property metadata.</param>
public StyledProperty(
string name,
Type ownerType,
TValue defaultValue,
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.Default,
Func<IPerspexObject, TValue, TValue> validate = null,
Action<IPerspexObject, bool> notifying = null)
: base(name, ownerType, defaultValue, inherits, defaultBindingMode, validate, notifying)
bool inherits,
StyledPropertyMetadata metadata)
: base(name, ownerType, inherits, metadata)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="StyledPropertyBase{T}"/> class.
/// </summary>
/// <param name="source">The property to add the owner to.</param>
/// <param name="ownerType">The type of the class that registers the property.</param>
internal StyledProperty(StyledPropertyBase<TValue> source, Type ownerType)
: base(source, ownerType)
{
}
/// <summary>
/// Registers the property on another type.
/// </summary>
/// <typeparam name="TOwner">The type of the additional owner.</typeparam>
/// <returns>The property.</returns>
public StyledProperty<TValue> AddOwner<TOwner>()
{
PerspexPropertyRegistry.Instance.Register(typeof(TOwner), this);
return this;
}
}
}

151
src/Perspex.Base/StyledPropertyBase.cs

@ -2,9 +2,8 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Reflection;
using Perspex.Data;
using Perspex.Utilities;
namespace Perspex
{
@ -13,34 +12,21 @@ namespace Perspex
/// </summary>
public class StyledPropertyBase<TValue> : PerspexProperty<TValue>, IStyledPropertyAccessor
{
private readonly TValue _defaultValue;
private readonly Dictionary<Type, TValue> _defaultValues;
private bool _inherits;
private readonly Dictionary<Type, Func<IPerspexObject, TValue, TValue>> _validation;
/// <summary>
/// Initializes a new instance of the <see cref="StyledPropertyBase{T}"/> class.
/// </summary>
/// <param name="name">The name of the property.</param>
/// <param name="ownerType">The type of the class that registers the property.</param>
/// <param name="defaultValue">The default value of the property.</param>
/// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="defaultBindingMode">The default binding mode for the property.</param>
/// <param name="validate">A validation function.</param>
/// <param name="notifying">
/// A method that gets called before and after the property starts being notified on an
/// object; the bool argument will be true before and false afterwards. This callback is
/// intended to support IsDataContextChanging.
/// </param>
/// <param name="metadata">The property metadata.</param>
protected StyledPropertyBase(
string name,
Type ownerType,
TValue defaultValue,
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.Default,
Func<IPerspexObject, TValue, TValue> validate = null,
Action<IPerspexObject, bool> notifying = null)
: base(name, ownerType, defaultBindingMode, notifying)
bool inherits,
StyledPropertyMetadata metadata)
: base(name, ownerType, CheckMetadata(metadata))
{
Contract.Requires<ArgumentNullException>(name != null);
Contract.Requires<ArgumentNullException>(ownerType != null);
@ -50,16 +36,18 @@ namespace Perspex
throw new ArgumentException("'name' may not contain periods.");
}
_defaultValues = new Dictionary<Type, TValue>();
_validation = new Dictionary<Type, Func<IPerspexObject, TValue, TValue>>();
_defaultValue = defaultValue;
_inherits = inherits;
}
if (validate != null)
{
_validation.Add(ownerType, validate);
}
/// <summary>
/// Initializes a new instance of the <see cref="StyledPropertyBase{T}"/> class.
/// </summary>
/// <param name="source">The property to add the owner to.</param>
/// <param name="ownerType">The type of the class that registers the property.</param>
protected StyledPropertyBase(StyledPropertyBase<TValue> source, Type ownerType)
: base(source, ownerType)
{
_inherits = source.Inherits;
}
/// <summary>
@ -79,19 +67,7 @@ namespace Perspex
{
Contract.Requires<ArgumentNullException>(type != null);
while (type != null)
{
TValue result;
if (_defaultValues.TryGetValue(type, out result))
{
return result;
}
type = type.GetTypeInfo().BaseType;
}
return _defaultValue;
return (TValue)(GetMetadata(type) as StyledPropertyMetadata)?.DefaultValue;
}
/// <summary>
@ -103,21 +79,22 @@ namespace Perspex
/// </returns>
public Func<IPerspexObject, TValue, TValue> GetValidationFunc(Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
return null;
////Contract.Requires<ArgumentNullException>(type != null);
while (type != null)
{
Func<IPerspexObject, TValue, TValue> result;
////while (type != null)
////{
//// Func<IPerspexObject, TValue, TValue> result;
if (_validation.TryGetValue(type, out result))
{
return result;
}
//// if (_validation.TryGetValue(type, out result))
//// {
//// return result;
//// }
type = type.GetTypeInfo().BaseType;
}
//// type = type.GetTypeInfo().BaseType;
////}
return null;
////return null;
}
/// <summary>
@ -137,14 +114,7 @@ namespace Perspex
/// <param name="defaultValue">The default value.</param>
public void OverrideDefaultValue(Type type, TValue defaultValue)
{
Contract.Requires<ArgumentNullException>(type != null);
if (_defaultValues.ContainsKey(type))
{
throw new InvalidOperationException("Default value is already set for this property.");
}
_defaultValues.Add(type, defaultValue);
OverrideMetadata(type, new StyledPropertyMetadata(defaultValue));
}
/// <summary>
@ -155,14 +125,15 @@ namespace Perspex
public void OverrideValidation<T>(Func<T, TValue, TValue> validation)
where T : IPerspexObject
{
var type = typeof(T);
throw new NotImplementedException();
////var type = typeof(T);
if (_validation.ContainsKey(type))
{
throw new InvalidOperationException("Validation is already set for this property.");
}
////if (_validation.ContainsKey(type))
////{
//// throw new InvalidOperationException("Validation is already set for this property.");
////}
_validation.Add(type, Cast(validation));
////_validation.Add(type, Cast(validation));
}
/// <summary>
@ -172,14 +143,15 @@ namespace Perspex
/// <param name="validation">The validation function.</param>
public void OverrideValidation(Type type, Func<IPerspexObject, TValue, TValue> validation)
{
Contract.Requires<ArgumentNullException>(type != null);
throw new NotImplementedException();
//Contract.Requires<ArgumentNullException>(type != null);
if (_validation.ContainsKey(type))
{
throw new InvalidOperationException("Validation is already set for this property.");
}
//if (_validation.ContainsKey(type))
//{
// throw new InvalidOperationException("Validation is already set for this property.");
//}
_validation.Add(type, validation);
//_validation.Add(type, validation);
}
/// <summary>
@ -195,10 +167,45 @@ namespace Perspex
Func<IPerspexObject, object, object> IStyledPropertyAccessor.GetValidationFunc(Type type)
{
var typed = GetValidationFunc(type);
return (o, v) => typed(o, (TValue)v);
if (typed != null)
{
return (o, v) => typed(o, (TValue)v);
}
else
{
return null;
}
}
/// <inheritdoc/>
object IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultValue(type);
private static PropertyMetadata CheckMetadata(StyledPropertyMetadata metadata)
{
var valueType = typeof(TValue).GetTypeInfo();
if (metadata.DefaultValue != null)
{
var defaultType = metadata.DefaultValue.GetType().GetTypeInfo();
if (!valueType.IsAssignableFrom(defaultType))
{
throw new ArgumentException(
"Invalid default property value. " +
$"Expected {typeof(TValue)} but recieved {metadata.DefaultValue.GetType()}.");
}
}
else
{
if (!TypeUtilities.AcceptsNull(typeof(TValue)))
{
throw new ArgumentException(
$"Invalid default property value. Null is not a valid value for {typeof(TValue)}.");
}
}
return metadata;
}
}
}

63
src/Perspex.Base/StyledPropertyMetadata.cs

@ -0,0 +1,63 @@
// 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 System;
using Perspex.Data;
namespace Perspex
{
/// <summary>
/// Styled perspex property metadata.
/// </summary>
public class StyledPropertyMetadata : PropertyMetadata
{
/// <summary>
/// Initializes a new instance of the <see cref="StyledPropertyMetadata"/> class.
/// </summary>
/// <param name="defaultValue">The default value of the property.</param>
/// <param name="validate">A validation function.</param>
/// <param name="defaultBindingMode">The default binding mode.</param>
/// <param name="notifyingCallback">The property notifying callback.</param>
public StyledPropertyMetadata(
object defaultValue,
Func<IPerspexObject, object, object> validate = null,
BindingMode defaultBindingMode = BindingMode.Default,
Action<IPerspexObject, bool> notifyingCallback = null)
: base(defaultBindingMode, notifyingCallback)
{
DefaultValue = defaultValue;
Validate = validate;
}
/// <summary>
/// Gets the default value for the property.
/// </summary>
public object DefaultValue { get; private set; }
/// <summary>
/// Gets the validation callback.
/// </summary>
public Func<IPerspexObject, object, object> Validate { get; private set; }
/// <inheritdoc/>
public override void Merge(PropertyMetadata baseMetadata, PerspexProperty property)
{
base.Merge(baseMetadata, property);
var src = baseMetadata as StyledPropertyMetadata;
if (src != null)
{
if (DefaultValue == null)
{
DefaultValue = src.DefaultValue;
}
if (Validate != null)
{
Validate = src.Validate;
}
}
}
}
}

8
src/Perspex.Controls/Border.cs

@ -13,25 +13,25 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="Background"/> property.
/// </summary>
public static readonly PerspexProperty<Brush> BackgroundProperty =
public static readonly StyledProperty<Brush> BackgroundProperty =
PerspexProperty.Register<Border, Brush>(nameof(Background));
/// <summary>
/// Defines the <see cref="BorderBrush"/> property.
/// </summary>
public static readonly PerspexProperty<Brush> BorderBrushProperty =
public static readonly StyledProperty<Brush> BorderBrushProperty =
PerspexProperty.Register<Border, Brush>(nameof(BorderBrush));
/// <summary>
/// Defines the <see cref="BorderThickness"/> property.
/// </summary>
public static readonly PerspexProperty<double> BorderThicknessProperty =
public static readonly StyledProperty<double> BorderThicknessProperty =
PerspexProperty.Register<Border, double>(nameof(BorderThickness));
/// <summary>
/// Defines the <see cref="CornerRadius"/> property.
/// </summary>
public static readonly PerspexProperty<float> CornerRadiusProperty =
public static readonly StyledProperty<float> CornerRadiusProperty =
PerspexProperty.Register<Border, float>(nameof(CornerRadius));
/// <summary>

10
src/Perspex.Controls/Button.cs

@ -35,31 +35,31 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="ClickMode"/> property.
/// </summary>
public static readonly PerspexProperty<ClickMode> ClickModeProperty =
public static readonly StyledProperty<ClickMode> ClickModeProperty =
PerspexProperty.Register<Button, ClickMode>(nameof(ClickMode));
/// <summary>
/// Defines the <see cref="Command"/> property.
/// </summary>
public static readonly PerspexProperty<ICommand> CommandProperty =
public static readonly StyledProperty<ICommand> CommandProperty =
PerspexProperty.Register<Button, ICommand>(nameof(Command));
/// <summary>
/// Defines the <see cref="HotKey"/> property.
/// </summary>
public static readonly PerspexProperty<KeyGesture> HotKeyProperty =
public static readonly StyledProperty<KeyGesture> HotKeyProperty =
HotKeyManager.HotKeyProperty.AddOwner<Button>();
/// <summary>
/// Defines the <see cref="CommandParameter"/> property.
/// </summary>
public static readonly PerspexProperty<object> CommandParameterProperty =
public static readonly StyledProperty<object> CommandParameterProperty =
PerspexProperty.Register<Button, object>(nameof(CommandParameter));
/// <summary>
/// Defines the <see cref="IsDefaultProperty"/> property.
/// </summary>
public static readonly PerspexProperty<bool> IsDefaultProperty =
public static readonly StyledProperty<bool> IsDefaultProperty =
PerspexProperty.Register<Button, bool>(nameof(IsDefault));
/// <summary>

5
src/Perspex.Controls/Carousel.cs

@ -17,11 +17,12 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="Transition"/> property.
/// </summary>
public static readonly PerspexProperty<IPageTransition> TransitionProperty =
public static readonly StyledProperty<IPageTransition> TransitionProperty =
PerspexProperty.Register<Carousel, IPageTransition>("Transition");
/// <summary>
/// The default value of <see cref="IReparentingControl"/> for <see cref="Carousel"/>.
/// The default value of <see cref="ItemsControl.ItemsPanelProperty"/> for
/// <see cref="Carousel"/>.
/// </summary>
private static readonly ITemplate<IPanel> PanelTemplate =
new FuncTemplate<IPanel>(() => new Panel());

9
src/Perspex.Controls/ContentControl.cs

@ -1,9 +1,6 @@
// 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 System;
using System.Linq;
using System.Reactive.Linq;
using Perspex.Controls.Mixins;
using Perspex.Controls.Presenters;
using Perspex.Controls.Primitives;
@ -21,19 +18,19 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="Content"/> property.
/// </summary>
public static readonly PerspexProperty<object> ContentProperty =
public static readonly StyledProperty<object> ContentProperty =
PerspexProperty.Register<ContentControl, object>(nameof(Content));
/// <summary>
/// Defines the <see cref="HorizontalContentAlignment"/> property.
/// </summary>
public static readonly PerspexProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
public static readonly StyledProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
PerspexProperty.Register<ContentControl, HorizontalAlignment>(nameof(HorizontalContentAlignment));
/// <summary>
/// Defines the <see cref="VerticalContentAlignment"/> property.
/// </summary>
public static readonly PerspexProperty<VerticalAlignment> VerticalContentAlignmentProperty =
public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
PerspexProperty.Register<ContentControl, VerticalAlignment>(nameof(VerticalContentAlignment));
/// <summary>

4
src/Perspex.Controls/Control.cs

@ -14,8 +14,6 @@ using Perspex.Controls.Templates;
using Perspex.Data;
using Perspex.Input;
using Perspex.Interactivity;
using Perspex.LogicalTree;
using Perspex.Rendering;
using Perspex.Styling;
namespace Perspex.Controls
@ -508,7 +506,7 @@ namespace Perspex.Controls
/// </summary>
/// <param name="o">The object on which the DataContext is changing.</param>
/// <param name="notifying">Whether the notifcation is beginning or ending.</param>
private static void DataContextNotifying(PerspexObject o, bool notifying)
private static void DataContextNotifying(IPerspexObject o, bool notifying)
{
var control = o as Control;

5
src/Perspex.Controls/Decorator.cs

@ -1,7 +1,6 @@
// 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.Collections;
using Perspex.Metadata;
namespace Perspex.Controls
@ -14,13 +13,13 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="Child"/> property.
/// </summary>
public static readonly PerspexProperty<Control> ChildProperty =
public static readonly StyledProperty<Control> ChildProperty =
PerspexProperty.Register<Decorator, Control>(nameof(Child));
/// <summary>
/// Defines the <see cref="Padding"/> property.
/// </summary>
public static readonly PerspexProperty<Thickness> PaddingProperty =
public static readonly StyledProperty<Thickness> PaddingProperty =
PerspexProperty.Register<Decorator, Thickness>(nameof(Padding));
/// <summary>

4
src/Perspex.Controls/HotkeyManager.cs

@ -12,8 +12,8 @@ namespace Perspex.Controls
{
public class HotKeyManager
{
public static readonly PerspexProperty<KeyGesture> HotKeyProperty
= PerspexProperty.RegisterAttached<Control, KeyGesture>("HotKey", typeof (HotKeyManager));
public static readonly AttachedProperty<KeyGesture> HotKeyProperty
= PerspexProperty.RegisterAttached<Control, KeyGesture>("HotKey", typeof(HotKeyManager));
class HotkeyCommandWrapper : ICommand
{

8
src/Perspex.Controls/ItemsControl.cs

@ -1,7 +1,6 @@
// 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 System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
@ -14,7 +13,6 @@ using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using Perspex.Controls.Utils;
using Perspex.Metadata;
using Perspex.Styling;
namespace Perspex.Controls
{
@ -33,19 +31,19 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="Items"/> property.
/// </summary>
public static readonly PerspexProperty<IEnumerable> ItemsProperty =
public static readonly DirectProperty<ItemsControl, IEnumerable> ItemsProperty =
PerspexProperty.RegisterDirect<ItemsControl, IEnumerable>(nameof(Items), o => o.Items, (o, v) => o.Items = v);
/// <summary>
/// Defines the <see cref="ItemsPanel"/> property.
/// </summary>
public static readonly PerspexProperty<ITemplate<IPanel>> ItemsPanelProperty =
public static readonly StyledProperty<ITemplate<IPanel>> ItemsPanelProperty =
PerspexProperty.Register<ItemsControl, ITemplate<IPanel>>(nameof(ItemsPanel), DefaultPanel);
/// <summary>
/// Defines the <see cref="MemberSelector"/> property.
/// </summary>
public static readonly PerspexProperty<IMemberSelector> MemberSelectorProperty =
public static readonly StyledProperty<IMemberSelector> MemberSelectorProperty =
PerspexProperty.Register<ItemsControl, IMemberSelector>(nameof(MemberSelector));
private IEnumerable _items = new PerspexList<object>();

2
src/Perspex.Controls/ListBoxItem.cs

@ -13,7 +13,7 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="IsSelected"/> property.
/// </summary>
public static readonly PerspexProperty<bool> IsSelectedProperty =
public static readonly StyledProperty<bool> IsSelectedProperty =
PerspexProperty.Register<ListBoxItem, bool>(nameof(IsSelected));
/// <summary>

10
src/Perspex.Controls/MenuItem.cs

@ -21,31 +21,31 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="Command"/> property.
/// </summary>
public static readonly PerspexProperty<ICommand> CommandProperty =
public static readonly StyledProperty<ICommand> CommandProperty =
Button.CommandProperty.AddOwner<MenuItem>();
/// <summary>
/// Defines the <see cref="HotKey"/> property.
/// </summary>
public static readonly PerspexProperty<KeyGesture> HotKeyProperty =
public static readonly StyledProperty<KeyGesture> HotKeyProperty =
HotKeyManager.HotKeyProperty.AddOwner<MenuItem>();
/// <summary>
/// Defines the <see cref="CommandParameter"/> property.
/// </summary>
public static readonly PerspexProperty<object> CommandParameterProperty =
public static readonly StyledProperty<object> CommandParameterProperty =
Button.CommandParameterProperty.AddOwner<MenuItem>();
/// <summary>
/// Defines the <see cref="Icon"/> property.
/// </summary>
public static readonly PerspexProperty<object> IconProperty =
public static readonly StyledProperty<object> IconProperty =
PerspexProperty.Register<MenuItem, object>(nameof(Icon));
/// <summary>
/// Defines the <see cref="IsSelected"/> property.
/// </summary>
public static readonly PerspexProperty<bool> IsSelectedProperty =
public static readonly StyledProperty<bool> IsSelectedProperty =
ListBoxItem.IsSelectedProperty.AddOwner<MenuItem>();
/// <summary>

9
src/Perspex.Controls/Presenters/ScrollContentPresenter.cs

@ -20,7 +20,7 @@ namespace Perspex.Controls.Presenters
/// <summary>
/// Defines the <see cref="Extent"/> property.
/// </summary>
public static readonly PerspexProperty<Size> ExtentProperty =
public static readonly DirectProperty<ScrollContentPresenter, Size> ExtentProperty =
ScrollViewer.ExtentProperty.AddOwner<ScrollContentPresenter>(
o => o.Extent,
(o, v) => o.Extent = v);
@ -28,7 +28,7 @@ namespace Perspex.Controls.Presenters
/// <summary>
/// Defines the <see cref="Offset"/> property.
/// </summary>
public static readonly PerspexProperty<Vector> OffsetProperty =
public static readonly DirectProperty<ScrollContentPresenter, Vector> OffsetProperty =
ScrollViewer.OffsetProperty.AddOwner<ScrollContentPresenter>(
o => o.Offset,
(o, v) => o.Offset = v);
@ -36,7 +36,7 @@ namespace Perspex.Controls.Presenters
/// <summary>
/// Defines the <see cref="Viewport"/> property.
/// </summary>
public static readonly PerspexProperty<Size> ViewportProperty =
public static readonly DirectProperty<ScrollContentPresenter, Size> ViewportProperty =
ScrollViewer.ViewportProperty.AddOwner<ScrollContentPresenter>(
o => o.Viewport,
(o, v) => o.Viewport = v);
@ -44,7 +44,7 @@ namespace Perspex.Controls.Presenters
/// <summary>
/// Defines the <see cref="CanScrollHorizontally"/> property.
/// </summary>
public static readonly PerspexProperty<bool> CanScrollHorizontallyProperty =
public static readonly StyledProperty<bool> CanScrollHorizontallyProperty =
PerspexProperty.Register<ScrollContentPresenter, bool>("CanScrollHorizontally", true);
private Size _extent;
@ -59,7 +59,6 @@ namespace Perspex.Controls.Presenters
static ScrollContentPresenter()
{
ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true);
OffsetProperty.OverrideValidation<ScrollContentPresenter>(ValidateOffset);
AffectsArrange(OffsetProperty);
}

2
src/Perspex.Controls/Primitives/HeaderedContentControl.cs

@ -11,7 +11,7 @@ namespace Perspex.Controls.Primitives
/// <summary>
/// Defines the <see cref="Header"/> property.
/// </summary>
public static readonly PerspexProperty<object> HeaderProperty =
public static readonly StyledProperty<object> HeaderProperty =
PerspexProperty.Register<ContentControl, object>("Header");
/// <summary>

2
src/Perspex.Controls/Primitives/HeaderedSelectingControl.cs

@ -1,8 +1,6 @@
// 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 System;
using System.Linq;
using Perspex.Controls.Mixins;
using Perspex.Controls.Presenters;

1
src/Perspex.Controls/Primitives/PopupRoot.cs

@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Perspex.Collections;
using Perspex.Controls.Platform;
using Perspex.Controls.Presenters;
using Perspex.Interactivity;

6
src/Perspex.Controls/Primitives/RangeBase.cs

@ -14,7 +14,7 @@ namespace Perspex.Controls.Primitives
/// <summary>
/// Defines the <see cref="Minimum"/> property.
/// </summary>
public static readonly PerspexProperty<double> MinimumProperty =
public static readonly DirectProperty<RangeBase, double> MinimumProperty =
PerspexProperty.RegisterDirect<RangeBase, double>(
nameof(Minimum),
o => o.Minimum,
@ -23,7 +23,7 @@ namespace Perspex.Controls.Primitives
/// <summary>
/// Defines the <see cref="Maximum"/> property.
/// </summary>
public static readonly PerspexProperty<double> MaximumProperty =
public static readonly DirectProperty<RangeBase, double> MaximumProperty =
PerspexProperty.RegisterDirect<RangeBase, double>(
nameof(Maximum),
o => o.Maximum,
@ -32,7 +32,7 @@ namespace Perspex.Controls.Primitives
/// <summary>
/// Defines the <see cref="Value"/> property.
/// </summary>
public static readonly PerspexProperty<double> ValueProperty =
public static readonly DirectProperty<RangeBase, double> ValueProperty =
PerspexProperty.RegisterDirect<RangeBase, double>(
nameof(Value),
o => o.Value,

6
src/Perspex.Controls/Primitives/ScrollBar.cs

@ -16,19 +16,19 @@ namespace Perspex.Controls.Primitives
/// <summary>
/// Defines the <see cref="ViewportSize"/> property.
/// </summary>
public static readonly PerspexProperty<double> ViewportSizeProperty =
public static readonly StyledProperty<double> ViewportSizeProperty =
PerspexProperty.Register<ScrollBar, double>(nameof(ViewportSize), defaultValue: double.NaN);
/// <summary>
/// Defines the <see cref="Visibility"/> property.
/// </summary>
public static readonly PerspexProperty<ScrollBarVisibility> VisibilityProperty =
public static readonly StyledProperty<ScrollBarVisibility> VisibilityProperty =
PerspexProperty.Register<ScrollBar, ScrollBarVisibility>(nameof(Visibility));
/// <summary>
/// Defines the <see cref="Orientation"/> property.
/// </summary>
public static readonly PerspexProperty<Orientation> OrientationProperty =
public static readonly StyledProperty<Orientation> OrientationProperty =
PerspexProperty.Register<ScrollBar, Orientation>(nameof(Orientation));
/// <summary>

8
src/Perspex.Controls/Primitives/SelectingItemsControl.cs

@ -40,7 +40,7 @@ namespace Perspex.Controls.Primitives
/// <summary>
/// Defines the <see cref="SelectedIndex"/> property.
/// </summary>
public static readonly PerspexProperty<int> SelectedIndexProperty =
public static readonly DirectProperty<SelectingItemsControl, int> SelectedIndexProperty =
PerspexProperty.RegisterDirect<SelectingItemsControl, int>(
nameof(SelectedIndex),
o => o.SelectedIndex,
@ -49,7 +49,7 @@ namespace Perspex.Controls.Primitives
/// <summary>
/// Defines the <see cref="SelectedItem"/> property.
/// </summary>
public static readonly PerspexProperty<object> SelectedItemProperty =
public static readonly DirectProperty<SelectingItemsControl, object> SelectedItemProperty =
PerspexProperty.RegisterDirect<SelectingItemsControl, object>(
nameof(SelectedItem),
o => o.SelectedItem,
@ -58,7 +58,7 @@ namespace Perspex.Controls.Primitives
/// <summary>
/// Defines the <see cref="SelectedItems"/> property.
/// </summary>
protected static readonly PerspexProperty<IList> SelectedItemsProperty =
protected static readonly DirectProperty<SelectingItemsControl, IList> SelectedItemsProperty =
PerspexProperty.RegisterDirect<SelectingItemsControl, IList>(
nameof(SelectedItems),
o => o.SelectedItems,
@ -67,7 +67,7 @@ namespace Perspex.Controls.Primitives
/// <summary>
/// Defines the <see cref="SelectionMode"/> property.
/// </summary>
protected static readonly PerspexProperty<SelectionMode> SelectionModeProperty =
protected static readonly StyledProperty<SelectionMode> SelectionModeProperty =
PerspexProperty.Register<SelectingItemsControl, SelectionMode>(
nameof(SelectionMode));

17
src/Perspex.Controls/Primitives/TemplatedControl.cs

@ -4,7 +4,6 @@
using System;
using System.Linq;
using System.Reactive.Linq;
using Perspex.Controls.Presenters;
using Perspex.Controls.Templates;
using Perspex.Data;
using Perspex.Interactivity;
@ -24,49 +23,49 @@ namespace Perspex.Controls.Primitives
/// <summary>
/// Defines the <see cref="Background"/> property.
/// </summary>
public static readonly PerspexProperty<Brush> BackgroundProperty =
public static readonly StyledProperty<Brush> BackgroundProperty =
Border.BackgroundProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="BorderBrush"/> property.
/// </summary>
public static readonly PerspexProperty<Brush> BorderBrushProperty =
public static readonly StyledProperty<Brush> BorderBrushProperty =
Border.BorderBrushProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="BorderThickness"/> property.
/// </summary>
public static readonly PerspexProperty<double> BorderThicknessProperty =
public static readonly StyledProperty<double> BorderThicknessProperty =
Border.BorderThicknessProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="FontFamily"/> property.
/// </summary>
public static readonly PerspexProperty<string> FontFamilyProperty =
public static readonly StyledProperty<string> FontFamilyProperty =
TextBlock.FontFamilyProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="FontSize"/> property.
/// </summary>
public static readonly PerspexProperty<double> FontSizeProperty =
public static readonly StyledProperty<double> FontSizeProperty =
TextBlock.FontSizeProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="FontStyle"/> property.
/// </summary>
public static readonly PerspexProperty<FontStyle> FontStyleProperty =
public static readonly StyledProperty<FontStyle> FontStyleProperty =
TextBlock.FontStyleProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="Foreground"/> property.
/// </summary>
public static readonly PerspexProperty<Brush> ForegroundProperty =
public static readonly StyledProperty<Brush> ForegroundProperty =
TextBlock.ForegroundProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="Padding"/> property.
/// </summary>
public static readonly PerspexProperty<Thickness> PaddingProperty =
public static readonly StyledProperty<Thickness> PaddingProperty =
Decorator.PaddingProperty.AddOwner<TemplatedControl>();
/// <summary>

24
src/Perspex.Controls/ScrollViewer.cs

@ -19,13 +19,13 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="CanScrollHorizontally"/> property.
/// </summary>
public static readonly PerspexProperty<bool> CanScrollHorizontallyProperty =
public static readonly AttachedProperty<bool> CanScrollHorizontallyProperty =
PerspexProperty.RegisterAttached<ScrollViewer, Control, bool>(nameof(CanScrollHorizontally), true);
/// <summary>
/// Defines the <see cref="Extent"/> property.
/// </summary>
public static readonly PerspexProperty<Size> ExtentProperty =
public static readonly DirectProperty<ScrollViewer, Size> ExtentProperty =
PerspexProperty.RegisterDirect<ScrollViewer, Size>(nameof(Extent),
o => o.Extent,
(o, v) => o.Extent = v);
@ -33,7 +33,7 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="Offset"/> property.
/// </summary>
public static readonly PerspexProperty<Vector> OffsetProperty =
public static readonly DirectProperty<ScrollViewer, Vector> OffsetProperty =
PerspexProperty.RegisterDirect<ScrollViewer, Vector>(
nameof(Offset),
o => o.Offset,
@ -42,7 +42,7 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="Viewport"/> property.
/// </summary>
public static readonly PerspexProperty<Size> ViewportProperty =
public static readonly DirectProperty<ScrollViewer, Size> ViewportProperty =
PerspexProperty.RegisterDirect<ScrollViewer, Size>(nameof(Viewport),
o => o.Viewport,
(o, v) => o.Viewport = v);
@ -54,7 +54,7 @@ namespace Perspex.Controls
/// There is no C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly PerspexProperty<double> HorizontalScrollBarMaximumProperty =
public static readonly StyledProperty<double> HorizontalScrollBarMaximumProperty =
PerspexProperty.Register<ScrollViewer, double>("HorizontalScrollBarMaximum");
/// <summary>
@ -64,7 +64,7 @@ namespace Perspex.Controls
/// There is no C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly PerspexProperty<double> HorizontalScrollBarValueProperty =
public static readonly StyledProperty<double> HorizontalScrollBarValueProperty =
PerspexProperty.Register<ScrollViewer, double>("HorizontalScrollBarValue");
/// <summary>
@ -74,7 +74,7 @@ namespace Perspex.Controls
/// There is no C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly PerspexProperty<double> HorizontalScrollBarViewportSizeProperty =
public static readonly StyledProperty<double> HorizontalScrollBarViewportSizeProperty =
PerspexProperty.Register<ScrollViewer, double>("HorizontalScrollBarViewportSize");
/// <summary>
@ -84,7 +84,7 @@ namespace Perspex.Controls
/// There is no C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly PerspexProperty<ScrollBarVisibility> HorizontalScrollBarVisibilityProperty =
public static readonly AttachedProperty<ScrollBarVisibility> HorizontalScrollBarVisibilityProperty =
PerspexProperty.RegisterAttached<ScrollBar, Control, ScrollBarVisibility>(
nameof(HorizontalScrollBarVisibility),
ScrollBarVisibility.Auto);
@ -96,7 +96,7 @@ namespace Perspex.Controls
/// There is no C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly PerspexProperty<double> VerticalScrollBarMaximumProperty =
public static readonly StyledProperty<double> VerticalScrollBarMaximumProperty =
PerspexProperty.Register<ScrollViewer, double>("VerticalScrollBarMaximum");
/// <summary>
@ -106,7 +106,7 @@ namespace Perspex.Controls
/// There is no C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly PerspexProperty<double> VerticalScrollBarValueProperty =
public static readonly StyledProperty<double> VerticalScrollBarValueProperty =
PerspexProperty.Register<ScrollViewer, double>("VerticalScrollBarValue");
/// <summary>
@ -116,13 +116,13 @@ namespace Perspex.Controls
/// There is no C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly PerspexProperty<double> VerticalScrollBarViewportSizeProperty =
public static readonly StyledProperty<double> VerticalScrollBarViewportSizeProperty =
PerspexProperty.Register<ScrollViewer, double>("VerticalScrollBarViewportSize");
/// <summary>
/// Defines the <see cref="VerticalScrollBarVisibility"/> property.
/// </summary>
public static readonly PerspexProperty<ScrollBarVisibility> VerticalScrollBarVisibilityProperty =
public static readonly AttachedProperty<ScrollBarVisibility> VerticalScrollBarVisibilityProperty =
PerspexProperty.RegisterAttached<ScrollViewer, Control, ScrollBarVisibility>(
nameof(VerticalScrollBarVisibility),
ScrollBarVisibility.Auto);

21
src/Perspex.Controls/TextBlock.cs

@ -18,13 +18,16 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="Background"/> property.
/// </summary>
public static readonly PerspexProperty<Brush> BackgroundProperty =
public static readonly StyledProperty<Brush> BackgroundProperty =
Border.BackgroundProperty.AddOwner<TextBlock>();
// TODO: Define these attached properties elswhere (e.g. on a Text class) and AddOwner
// them into TextBlock.
/// <summary>
/// Defines the <see cref="FontFamily"/> property.
/// </summary>
public static readonly PerspexProperty<string> FontFamilyProperty =
public static readonly AttachedProperty<string> FontFamilyProperty =
PerspexProperty.RegisterAttached<TextBlock, Control, string>(
nameof(FontFamily),
defaultValue: "Courier New",
@ -33,7 +36,7 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="FontSize"/> property.
/// </summary>
public static readonly PerspexProperty<double> FontSizeProperty =
public static readonly AttachedProperty<double> FontSizeProperty =
PerspexProperty.RegisterAttached<TextBlock, Control, double>(
nameof(FontSize),
defaultValue: 12,
@ -42,7 +45,7 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="FontStyle"/> property.
/// </summary>
public static readonly PerspexProperty<FontStyle> FontStyleProperty =
public static readonly AttachedProperty<FontStyle> FontStyleProperty =
PerspexProperty.RegisterAttached<TextBlock, Control, FontStyle>(
nameof(FontStyle),
inherits: true);
@ -50,7 +53,7 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="FontWeight"/> property.
/// </summary>
public static readonly PerspexProperty<FontWeight> FontWeightProperty =
public static readonly AttachedProperty<FontWeight> FontWeightProperty =
PerspexProperty.RegisterAttached<TextBlock, Control, FontWeight>(
nameof(FontWeight),
inherits: true,
@ -59,7 +62,7 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="Foreground"/> property.
/// </summary>
public static readonly PerspexProperty<Brush> ForegroundProperty =
public static readonly AttachedProperty<Brush> ForegroundProperty =
PerspexProperty.RegisterAttached<TextBlock, Control, Brush>(
nameof(Foreground),
new SolidColorBrush(0xff000000),
@ -68,19 +71,19 @@ namespace Perspex.Controls
/// <summary>
/// Defines the <see cref="Text"/> property.
/// </summary>
public static readonly PerspexProperty<string> TextProperty =
public static readonly StyledProperty<string> TextProperty =
PerspexProperty.Register<TextBlock, string>(nameof(Text), defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Defines the <see cref="TextAlignment"/> property.
/// </summary>
public static readonly PerspexProperty<TextAlignment> TextAlignmentProperty =
public static readonly StyledProperty<TextAlignment> TextAlignmentProperty =
PerspexProperty.Register<TextBlock, TextAlignment>(nameof(TextAlignment));
/// <summary>
/// Defines the <see cref="TextWrapping"/> property.
/// </summary>
public static readonly PerspexProperty<TextWrapping> TextWrappingProperty =
public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
PerspexProperty.Register<TextBlock, TextWrapping>(nameof(TextWrapping));
/// <summary>

21
src/Perspex.Controls/TextBox.cs

@ -20,34 +20,35 @@ namespace Perspex.Controls
{
public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost
{
public static readonly PerspexProperty<bool> AcceptsReturnProperty =
public static readonly StyledProperty<bool> AcceptsReturnProperty =
PerspexProperty.Register<TextBox, bool>("AcceptsReturn");
public static readonly PerspexProperty<bool> AcceptsTabProperty =
public static readonly StyledProperty<bool> AcceptsTabProperty =
PerspexProperty.Register<TextBox, bool>("AcceptsTab");
public static readonly PerspexProperty<int> CaretIndexProperty =
// TODO: Should CaretIndex, SelectionStart/End and Text be direct properties?
public static readonly StyledProperty<int> CaretIndexProperty =
PerspexProperty.Register<TextBox, int>("CaretIndex", validate: ValidateCaretIndex);
public static readonly PerspexProperty<int> SelectionStartProperty =
public static readonly StyledProperty<int> SelectionStartProperty =
PerspexProperty.Register<TextBox, int>("SelectionStart", validate: ValidateCaretIndex);
public static readonly PerspexProperty<int> SelectionEndProperty =
public static readonly StyledProperty<int> SelectionEndProperty =
PerspexProperty.Register<TextBox, int>("SelectionEnd", validate: ValidateCaretIndex);
public static readonly PerspexProperty<string> TextProperty =
public static readonly StyledProperty<string> TextProperty =
TextBlock.TextProperty.AddOwner<TextBox>();
public static readonly PerspexProperty<TextAlignment> TextAlignmentProperty =
public static readonly StyledProperty<TextAlignment> TextAlignmentProperty =
TextBlock.TextAlignmentProperty.AddOwner<TextBox>();
public static readonly PerspexProperty<TextWrapping> TextWrappingProperty =
public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
TextBlock.TextWrappingProperty.AddOwner<TextBox>();
public static readonly PerspexProperty<string> WatermarkProperty =
public static readonly StyledProperty<string> WatermarkProperty =
PerspexProperty.Register<TextBox, string>("Watermark");
public static readonly PerspexProperty<bool> UseFloatingWatermarkProperty =
public static readonly StyledProperty<bool> UseFloatingWatermarkProperty =
PerspexProperty.Register<TextBox, bool>("UseFloatingWatermark");
struct UndoRedoState : IEquatable<UndoRedoState>

2
src/Perspex.HtmlRenderer/HtmlControl.cs

@ -85,7 +85,7 @@ namespace Perspex.Controls.Html
public static readonly PerspexProperty TextProperty =
PropertyHelper.Register<HtmlControl, string>("Text", null, OnPerspexProperty_valueChanged);
public static readonly PerspexProperty<Brush> BackgroundProperty =
public static readonly StyledProperty<Brush> BackgroundProperty =
Border.BackgroundProperty.AddOwner<HtmlControl>();
public static readonly PerspexProperty BorderThicknessProperty =

14
src/Perspex.Input/InputElement.cs

@ -18,43 +18,43 @@ namespace Perspex.Input
/// <summary>
/// Defines the <see cref="Focusable"/> property.
/// </summary>
public static readonly PerspexProperty<bool> FocusableProperty =
public static readonly StyledProperty<bool> FocusableProperty =
PerspexProperty.Register<InputElement, bool>(nameof(Focusable));
/// <summary>
/// Defines the <see cref="IsEnabled"/> property.
/// </summary>
public static readonly PerspexProperty<bool> IsEnabledProperty =
public static readonly StyledProperty<bool> IsEnabledProperty =
PerspexProperty.Register<InputElement, bool>(nameof(IsEnabled), true);
/// <summary>
/// Defines the <see cref="IsEnabledCore"/> property.
/// </summary>
public static readonly PerspexProperty<bool> IsEnabledCoreProperty =
public static readonly StyledProperty<bool> IsEnabledCoreProperty =
PerspexProperty.Register<InputElement, bool>("IsEnabledCore", true);
/// <summary>
/// Gets or sets associated mouse cursor.
/// </summary>
public static readonly PerspexProperty<Cursor> CursorProperty =
public static readonly StyledProperty<Cursor> CursorProperty =
PerspexProperty.Register<InputElement, Cursor>("Cursor", null, true);
/// <summary>
/// Defines the <see cref="IsFocused"/> property.
/// </summary>
public static readonly PerspexProperty<bool> IsFocusedProperty =
public static readonly DirectProperty<InputElement, bool> IsFocusedProperty =
PerspexProperty.RegisterDirect<InputElement, bool>("IsFocused", o => o.IsFocused);
/// <summary>
/// Defines the <see cref="IsHitTestVisible"/> property.
/// </summary>
public static readonly PerspexProperty<bool> IsHitTestVisibleProperty =
public static readonly StyledProperty<bool> IsHitTestVisibleProperty =
PerspexProperty.Register<InputElement, bool>("IsHitTestVisible", true);
/// <summary>
/// Defines the <see cref="IsPointerOver"/> property.
/// </summary>
public static readonly PerspexProperty<bool> IsPointerOverProperty =
public static readonly DirectProperty<InputElement, bool> IsPointerOverProperty =
PerspexProperty.RegisterDirect<InputElement, bool>("IsPointerOver", o => o.IsPointerOver);
/// <summary>

6
src/Perspex.Input/KeyboardNavigation.cs

@ -15,7 +15,7 @@ namespace Perspex.Input
/// The DirectionalNavigation attached property defines how pressing arrow keys causes
/// focus to be navigated between the children of the container.
/// </remarks>
public static readonly PerspexProperty<KeyboardNavigationMode> DirectionalNavigationProperty =
public static readonly AttachedProperty<KeyboardNavigationMode> DirectionalNavigationProperty =
PerspexProperty.RegisterAttached<InputElement, KeyboardNavigationMode>(
"DirectionalNavigation",
typeof(KeyboardNavigation),
@ -28,7 +28,7 @@ namespace Perspex.Input
/// The TabNavigation attached property defines how pressing the Tab key causes focus to
/// be navigated between the children of the container.
/// </remarks>
public static readonly PerspexProperty<KeyboardNavigationMode> TabNavigationProperty =
public static readonly AttachedProperty<KeyboardNavigationMode> TabNavigationProperty =
PerspexProperty.RegisterAttached<InputElement, KeyboardNavigationMode>(
"TabNavigation",
typeof(KeyboardNavigation));
@ -41,7 +41,7 @@ namespace Perspex.Input
/// attached property set to <see cref="KeyboardNavigationMode.Once"/>, this property
/// defines to which child the focus should move.
/// </remarks>
public static readonly PerspexProperty<IInputElement> TabOnceActiveElementProperty =
public static readonly AttachedProperty<IInputElement> TabOnceActiveElementProperty =
PerspexProperty.RegisterAttached<InputElement, IInputElement>(
"TabOnceActiveElement",
typeof(KeyboardNavigation));

16
src/Perspex.SceneGraph/Visual.cs

@ -32,49 +32,49 @@ namespace Perspex
/// <summary>
/// Defines the <see cref="Bounds"/> property.
/// </summary>
public static readonly PerspexProperty<Rect> BoundsProperty =
public static readonly DirectProperty<Visual, Rect> BoundsProperty =
PerspexProperty.RegisterDirect<Visual, Rect>(nameof(Bounds), o => o.Bounds);
/// <summary>
/// Defines the <see cref="ClipToBounds"/> property.
/// </summary>
public static readonly PerspexProperty<bool> ClipToBoundsProperty =
public static readonly StyledProperty<bool> ClipToBoundsProperty =
PerspexProperty.Register<Visual, bool>(nameof(ClipToBounds));
/// <summary>
/// Defines the <see cref="IsVisibleProperty"/> property.
/// </summary>
public static readonly PerspexProperty<bool> IsVisibleProperty =
public static readonly StyledProperty<bool> IsVisibleProperty =
PerspexProperty.Register<Visual, bool>(nameof(IsVisible), true);
/// <summary>
/// Defines the <see cref="Opacity"/> property.
/// </summary>
public static readonly PerspexProperty<double> OpacityProperty =
public static readonly StyledProperty<double> OpacityProperty =
PerspexProperty.Register<Visual, double>(nameof(Opacity), 1);
/// <summary>
/// Defines the <see cref="RenderTransform"/> property.
/// </summary>
public static readonly PerspexProperty<Transform> RenderTransformProperty =
public static readonly StyledProperty<Transform> RenderTransformProperty =
PerspexProperty.Register<Visual, Transform>(nameof(RenderTransform));
/// <summary>
/// Defines the <see cref="TransformOrigin"/> property.
/// </summary>
public static readonly PerspexProperty<RelativePoint> TransformOriginProperty =
public static readonly StyledProperty<RelativePoint> TransformOriginProperty =
PerspexProperty.Register<Visual, RelativePoint>(nameof(TransformOrigin), defaultValue: RelativePoint.Center);
/// <summary>
/// Defines the <see cref="IVisual.VisualParent"/> property.
/// </summary>
public static readonly PerspexProperty<IVisual> VisualParentProperty =
public static readonly DirectProperty<Visual, IVisual> VisualParentProperty =
PerspexProperty.RegisterDirect<Visual, IVisual>("VisualParent", o => o._visualParent);
/// <summary>
/// Defines the <see cref="ZIndex"/> property.
/// </summary>
public static readonly PerspexProperty<int> ZIndexProperty =
public static readonly StyledProperty<int> ZIndexProperty =
PerspexProperty.Register<Visual, int>(nameof(ZIndex));
/// <summary>

2
src/Perspex.Styling/Styling/Setter.cs

@ -112,7 +112,7 @@ namespace Perspex.Styling
if (mode == BindingMode.Default)
{
mode = property.DefaultBindingMode;
mode = property.GetMetadata(control.GetType()).DefaultBindingMode;
}
control.Bind(

26
tests/Perspex.Base.UnitTests/AttachedPropertyTests.cs

@ -0,0 +1,26 @@
// 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 Xunit;
namespace Perspex.Base.UnitTests
{
public class AttachedPropertyTests
{
[Fact]
public void IsAttached_Returns_True()
{
var property = new AttachedProperty<string>(
"Foo",
typeof(Class1),
false,
new StyledPropertyMetadata(null));
Assert.True(property.IsAttached);
}
private class Class1 : PerspexObject
{
}
}
}

87
tests/Perspex.Base.UnitTests/DirectPropertyTests.cs

@ -0,0 +1,87 @@
// 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 System;
using Perspex.Data;
using Xunit;
namespace Perspex.Base.UnitTests
{
public class DirectPropertyTests
{
[Fact]
public void Initialized_Observable_Fired()
{
bool invoked = false;
Class1.FooProperty.Initialized.Subscribe(x =>
{
Assert.Equal(PerspexProperty.UnsetValue, x.OldValue);
Assert.Equal("foo", x.NewValue);
Assert.Equal(BindingPriority.Unset, x.Priority);
invoked = true;
});
var target = new Class1();
Assert.True(invoked);
}
[Fact]
public void IsDirect_Property_Returns_True()
{
var target = new DirectProperty<Class1, string>("test", o => null);
Assert.True(target.IsDirect);
}
[Fact]
public void AddOwnered_Property_Should_Equal_Original()
{
var p1 = Class1.FooProperty;
var p2 = p1.AddOwner<Class2>(o => null, (o, v) => { });
Assert.NotSame(p1, p2);
Assert.True(p1.Equals(p2));
Assert.Equal(p1.GetHashCode(), p2.GetHashCode());
Assert.True(p1 == p2);
}
[Fact]
public void AddOwnered_Property_Should_Have_OwnerType_Set()
{
var p1 = Class1.FooProperty;
var p2 = p1.AddOwner<Class2>(o => null, (o, v) => { });
Assert.Equal(typeof(Class2), p2.OwnerType);
}
[Fact]
public void AddOwnered_Properties_Should_Share_Observables()
{
var p1 = Class1.FooProperty;
var p2 = p1.AddOwner<Class2>(o => null, (o, v) => { });
Assert.Same(p1.Changed, p2.Changed);
Assert.Same(p1.Initialized, p2.Initialized);
}
private class Class1 : PerspexObject
{
public static readonly DirectProperty<Class1, string> FooProperty =
PerspexProperty.RegisterDirect<Class1, string>("Foo", o => o.Foo, (o, v) => o.Foo = v);
private string _foo = "foo";
public string Foo
{
get { return _foo; }
set { SetAndRaise(FooProperty, ref _foo, value); }
}
}
private class Class2 : PerspexObject
{
}
}
}

3
tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj

@ -81,6 +81,7 @@
<Compile Include="Collections\PerspexDictionaryTests.cs" />
<Compile Include="Collections\PerspexListTests.cs" />
<Compile Include="Collections\PropertyChangedTracker.cs" />
<Compile Include="AttachedPropertyTests.cs" />
<Compile Include="PerspexObjectTests_Direct.cs" />
<Compile Include="PerspexObjectTests_GetSubject.cs" />
<Compile Include="PerspexObjectTests_GetObservable.cs" />
@ -91,6 +92,8 @@
<Compile Include="PerspexObjectTests_SetValue.cs" />
<Compile Include="PerspexObjectTests_GetValue.cs" />
<Compile Include="PerspexObjectTests_Metadata.cs" />
<Compile Include="DirectPropertyTests.cs" />
<Compile Include="StyledPropertyTests.cs" />
<Compile Include="PerspexPropertyTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PriorityValueTests.cs" />

4
tests/Perspex.Base.UnitTests/PerspexPropertyRegistryTests.cs

@ -160,7 +160,7 @@ namespace Perspex.Base.UnitTests
private class Class3 : Class1
{
public static readonly PerspexProperty<string> AttachedProperty =
public static readonly StyledProperty<string> AttachedProperty =
AttachedOwner.AttachedProperty.AddOwner<Class3>();
}
@ -170,7 +170,7 @@ namespace Perspex.Base.UnitTests
private class AttachedOwner
{
public static readonly PerspexProperty<string> AttachedProperty =
public static readonly AttachedProperty<string> AttachedProperty =
PerspexProperty.RegisterAttached<AttachedOwner, Class1, string>("Attached");
}
}

274
tests/Perspex.Base.UnitTests/PerspexPropertyTests.cs

@ -12,88 +12,71 @@ namespace Perspex.Base.UnitTests
[Fact]
public void Constructor_Sets_Properties()
{
PerspexProperty<string> target = new PerspexProperty<string>(
"test",
typeof(Class1),
"Foo",
false,
BindingMode.OneWay,
null);
var target = new TestProperty<string>("test", typeof(Class1));
Assert.Equal("test", target.Name);
Assert.Equal(typeof(string), target.PropertyType);
Assert.Equal(typeof(Class1), target.OwnerType);
Assert.Equal(false, target.Inherits);
}
[Fact]
public void Name_Cannot_Contain_Periods()
{
Assert.Throws<ArgumentException>(() => new PerspexProperty<string>(
"Foo.Bar",
typeof(Class1),
"Foo",
false,
BindingMode.OneWay,
null));
Assert.Throws<ArgumentException>(() => new TestProperty<string>("Foo.Bar", typeof(Class1)));
}
[Fact]
public void GetDefaultValue_Returns_Registered_Value()
public void GetMetadata_Returns_Supplied_Value()
{
PerspexProperty<string> target = new PerspexProperty<string>(
"test",
typeof(Class1),
"Foo",
false,
BindingMode.OneWay,
null);
Assert.Equal("Foo", target.GetDefaultValue<Class1>());
var metadata = new PropertyMetadata();
var target = new TestProperty<string>("test", typeof(Class1), metadata);
Assert.Same(metadata, target.GetMetadata<Class1>());
}
[Fact]
public void GetDefaultValue_Returns_Registered_Value_For_Not_Overridden_Class()
public void GetMetadata_Returns_Supplied_Value_For_Derived_Class()
{
PerspexProperty<string> target = new PerspexProperty<string>(
"test",
typeof(Class1),
"Foo",
false,
BindingMode.OneWay,
null);
Assert.Equal("Foo", target.GetDefaultValue<Class2>());
var metadata = new PropertyMetadata();
var target = new TestProperty<string>("test", typeof(Class1), metadata);
Assert.Same(metadata, target.GetMetadata<Class2>());
}
[Fact]
public void GetDefaultValue_Returns_Registered_Value_For_Unrelated_Class()
public void GetMetadata_Returns_Supplied_Value_For_Unrelated_Class()
{
PerspexProperty<string> target = new PerspexProperty<string>(
"test",
typeof(Class3),
"Foo",
false,
BindingMode.OneWay,
null);
Assert.Equal("Foo", target.GetDefaultValue<Class2>());
var metadata = new PropertyMetadata();
var target = new TestProperty<string>("test", typeof(Class3), metadata);
Assert.Same(metadata, target.GetMetadata<Class2>());
}
[Fact]
public void GetDefaultValue_Returns_Overridden_Value()
public void GetMetadata_Returns_Overridden_Value()
{
PerspexProperty<string> target = new PerspexProperty<string>(
"test",
typeof(Class1),
"Foo",
false,
BindingMode.OneWay,
null);
var metadata = new PropertyMetadata();
var overridden = new PropertyMetadata();
var target = new TestProperty<string>("test", typeof(Class1), metadata);
target.OverrideDefaultValue(typeof(Class2), "Bar");
target.OverrideMetadata<Class2>(overridden);
Assert.Equal("Bar", target.GetDefaultValue<Class2>());
Assert.Same(overridden, target.GetMetadata<Class2>());
}
[Fact]
public void OverrideMetadata_Should_Merge_Values()
{
var metadata = new PropertyMetadata(BindingMode.TwoWay);
var notify = (Action<IPerspexObject, bool>)((a, b) => { });
var overridden = new PropertyMetadata(notifyingCallback: notify);
var target = new TestProperty<string>("test", typeof(Class1), metadata);
target.OverrideMetadata<Class2>(overridden);
var result = target.GetMetadata<Class2>();
Assert.Equal(BindingMode.TwoWay, result.DefaultBindingMode);
Assert.Equal(notify, result.NotifyingCallback);
}
[Fact]
@ -126,22 +109,22 @@ namespace Perspex.Base.UnitTests
Assert.Equal("newvalue", value);
}
[Fact]
public void IsDirect_Property_Set_On_Direct_PerspexProperty()
{
PerspexProperty<string> target = new PerspexProperty<string>(
"test",
typeof(Class1),
o => null,
(o, v) => { });
////[Fact]
////public void IsDirect_Property_Set_On_Direct_PerspexProperty()
////{
//// PerspexProperty<string> target = new PerspexProperty<string>(
//// "test",
//// typeof(Class1),
//// o => null,
//// (o, v) => { });
Assert.True(target.IsDirect);
}
//// Assert.True(target.IsDirect);
////}
[Fact]
public void Property_Equals_Should_Handle_Null()
{
var p1 = new PerspexProperty<string>("p1", typeof(Class1));
var p1 = new TestProperty<string>("p1", typeof(Class1));
Assert.NotEqual(p1, null);
Assert.NotEqual(null, p1);
@ -151,80 +134,93 @@ namespace Perspex.Base.UnitTests
Assert.True((PerspexProperty)null == (PerspexProperty)null);
}
[Fact]
public void AddOwnered_Property_Should_Equal_Original()
{
var p1 = new PerspexProperty<string>("p1", typeof(Class1));
var p2 = p1.AddOwner<Class3>();
Assert.Equal(p1, p2);
Assert.Equal(p1.GetHashCode(), p2.GetHashCode());
Assert.True(p1 == p2);
}
[Fact]
public void AddOwnered_Property_Should_Have_OwnerType_Set()
{
var p1 = new PerspexProperty<string>("p1", typeof(Class1));
var p2 = p1.AddOwner<Class3>();
Assert.Equal(typeof(Class3), p2.OwnerType);
}
[Fact]
public void AddOwnered_Properties_Should_Share_Observables()
{
var p1 = new PerspexProperty<string>("p1", typeof(Class1));
var p2 = p1.AddOwner<Class3>();
Assert.Same(p1.Changed, p2.Changed);
Assert.Same(p1.Initialized, p2.Initialized);
}
[Fact]
public void AddOwnered_Direct_Property_Should_Equal_Original()
{
var p1 = new PerspexProperty<string>("d1", typeof(Class1), o => null, (o,v) => { });
var p2 = p1.AddOwner<Class3>(o => null, (o, v) => { });
Assert.Equal(p1, p2);
Assert.Equal(p1.GetHashCode(), p2.GetHashCode());
Assert.True(p1 == p2);
}
[Fact]
public void AddOwnered_Direct_Property_Should_Have_OwnerType_Set()
{
var p1 = new PerspexProperty<string>("d1", typeof(Class1), o => null, (o, v) => { });
var p2 = p1.AddOwner<Class3>(o => null, (o, v) => { });
Assert.Equal(typeof(Class3), p2.OwnerType);
}
[Fact]
public void AddOwnered_Direct_Properties_Should_Share_Observables()
{
var p1 = new PerspexProperty<string>("d1", typeof(Class1), o => null, (o, v) => { });
var p2 = p1.AddOwner<Class3>(o => null, (o, v) => { });
Assert.Same(p1.Changed, p2.Changed);
Assert.Same(p1.Initialized, p2.Initialized);
}
[Fact]
public void AddOwner_With_Getter_And_Setter_On_Standard_Property_Should_Throw()
{
var p1 = new PerspexProperty<string>("p1", typeof(Class1));
Assert.Throws<InvalidOperationException>(() => p1.AddOwner<Class3>(o => null, (o, v) => { }));
}
[Fact]
public void AddOwner_On_Direct_Property_Without_Getter_Or_Setter_Should_Throw()
{
var p1 = new PerspexProperty<string>("e1", typeof(Class1), o => null, (o, v) => { });
////[Fact]
////public void AddOwnered_Property_Should_Equal_Original()
////{
//// var p1 = new PerspexProperty<string>("p1", typeof(Class1));
//// var p2 = p1.AddOwner<Class3>();
//// Assert.Equal(p1, p2);
//// Assert.Equal(p1.GetHashCode(), p2.GetHashCode());
//// Assert.True(p1 == p2);
////}
////[Fact]
////public void AddOwnered_Property_Should_Have_OwnerType_Set()
////{
//// var p1 = new PerspexProperty<string>("p1", typeof(Class1));
//// var p2 = p1.AddOwner<Class3>();
//// Assert.Equal(typeof(Class3), p2.OwnerType);
////}
////[Fact]
////public void AddOwnered_Properties_Should_Share_Observables()
////{
//// var p1 = new PerspexProperty<string>("p1", typeof(Class1));
//// var p2 = p1.AddOwner<Class3>();
//// Assert.Same(p1.Changed, p2.Changed);
//// Assert.Same(p1.Initialized, p2.Initialized);
////}
////[Fact]
////public void AddOwnered_Direct_Property_Should_Equal_Original()
////{
//// var p1 = new PerspexProperty<string>("d1", typeof(Class1), o => null, (o,v) => { });
//// var p2 = p1.AddOwner<Class3>(o => null, (o, v) => { });
//// Assert.Equal(p1, p2);
//// Assert.Equal(p1.GetHashCode(), p2.GetHashCode());
//// Assert.True(p1 == p2);
////}
////[Fact]
////public void AddOwnered_Direct_Property_Should_Have_OwnerType_Set()
////{
//// var p1 = new PerspexProperty<string>("d1", typeof(Class1), o => null, (o, v) => { });
//// var p2 = p1.AddOwner<Class3>(o => null, (o, v) => { });
//// Assert.Equal(typeof(Class3), p2.OwnerType);
////}
////[Fact]
////public void AddOwnered_Direct_Properties_Should_Share_Observables()
////{
//// var p1 = new PerspexProperty<string>("d1", typeof(Class1), o => null, (o, v) => { });
//// var p2 = p1.AddOwner<Class3>(o => null, (o, v) => { });
//// Assert.Same(p1.Changed, p2.Changed);
//// Assert.Same(p1.Initialized, p2.Initialized);
////}
////[Fact]
////public void AddOwner_With_Getter_And_Setter_On_Standard_Property_Should_Throw()
////{
//// var p1 = new PerspexProperty<string>("p1", typeof(Class1));
//// Assert.Throws<InvalidOperationException>(() => p1.AddOwner<Class3>(o => null, (o, v) => { }));
////}
////[Fact]
////public void AddOwner_On_Direct_Property_Without_Getter_Or_Setter_Should_Throw()
////{
//// var p1 = new PerspexProperty<string>("e1", typeof(Class1), o => null, (o, v) => { });
//// Assert.Throws<InvalidOperationException>(() => p1.AddOwner<Class3>());
////}
private class TestProperty<TValue> : PerspexProperty<TValue>
{
public TestProperty(string name, Type ownerType, PropertyMetadata metadata = null)
: base(name, ownerType, metadata ?? new PropertyMetadata())
{
}
Assert.Throws<InvalidOperationException>(() => p1.AddOwner<Class3>());
public void OverrideMetadata<T>(PropertyMetadata metadata)
{
OverrideMetadata(typeof(T), metadata);
}
}
private class Class1 : PerspexObject

48
tests/Perspex.Base.UnitTests/StyledPropertyTests.cs

@ -0,0 +1,48 @@
// 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 Xunit;
namespace Perspex.Base.UnitTests
{
public class StyledPropertyTests
{
[Fact]
public void AddOwnered_Property_Should_Equal_Original()
{
var p1 = new StyledProperty<string>(
"p1",
typeof(Class1),
false,
new StyledPropertyMetadata(null));
var p2 = p1.AddOwner<Class2>();
Assert.Equal(p1, p2);
Assert.Equal(p1.GetHashCode(), p2.GetHashCode());
Assert.True(p1 == p2);
}
[Fact]
public void AddOwnered_Property_Should_Be_Same()
{
var p1 = new StyledProperty<string>(
"p1",
typeof(Class1),
false,
new StyledPropertyMetadata(null));
var p2 = p1.AddOwner<Class2>();
Assert.Same(p1, p2);
}
private class Class1 : PerspexObject
{
public static readonly PerspexProperty<string> FooProperty =
PerspexProperty.Register<Class1, string>("Foo", "default");
}
private class Class2 : PerspexObject
{
}
}
}
Loading…
Cancel
Save