Browse Source

WIP: Splitting PerspexProperty up

pull/387/merge
Steven Kirk 10 years ago
parent
commit
03e2ff8784
  1. 41
      src/Perspex.Base/AttachedProperty.cs
  2. 109
      src/Perspex.Base/DirectProperty.cs
  3. 31
      src/Perspex.Base/IDirectPropertyAccessor.cs
  4. 31
      src/Perspex.Base/IStyledPropertyAccessor.cs
  5. 6
      src/Perspex.Base/Perspex.Base.csproj
  6. 85
      src/Perspex.Base/PerspexObject.cs
  7. 405
      src/Perspex.Base/PerspexProperty.cs
  8. 191
      src/Perspex.Base/PerspexProperty`1.cs
  9. 40
      src/Perspex.Base/StyledProperty.cs
  10. 204
      src/Perspex.Base/StyledPropertyBase.cs
  11. 28
      src/Perspex.Controls/Grid.cs
  12. 6
      tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs
  13. 4
      tests/Perspex.Base.UnitTests/PerspexObjectTests_GetValue.cs
  14. 4
      tests/Perspex.Base.UnitTests/PerspexObjectTests_Inheritance.cs
  15. 4
      tests/Perspex.Base.UnitTests/PerspexObjectTests_Validation.cs

41
src/Perspex.Base/AttachedProperty.cs

@ -0,0 +1,41 @@
// 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>
/// An attached perspex property.
/// </summary>
/// <typeparam name="TValue">The type of the property's value.</typeparam>
public class AttachedProperty<TValue> : StyledPropertyBase<TValue>
{
/// <summary>
/// Initializes a new instance of the <see cref="AttachedProperty{TValue}"/> class.
/// </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>
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)
{
}
/// <inheritdoc/>
public override string FullName => OwnerType + "." + Name;
/// <inheritdoc/>
public override bool IsAttached => true;
}
}

109
src/Perspex.Base/DirectProperty.cs

@ -0,0 +1,109 @@
// 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;
namespace Perspex
{
/// <summary>
/// A direct perspex property.
/// </summary>
/// <typeparam name="TOwner">The class that registered the property.</typeparam>
/// <typeparam name="TValue">The type of the property's value.</typeparam>
/// <remarks>
/// Direct perspex properties are backed by a field on the object, but exposed via the
/// <see cref="PerspexProperty"/> system. They hold a getter and an optional setter which
/// allows the perspex property system to read and write the current value.
/// </remarks>
public class DirectProperty<TOwner, TValue> : PerspexProperty<TValue>, IDirectPropertyAccessor
where TOwner : IPerspexObject
{
/// <summary>
/// Initializes a new instance of the <see cref="DirectProperty{TOwner, TValue}"/> class.
/// </summary>
/// <param name="name">The name of the property.</param>
/// <param name="getter">Gets the current value of the property.</param>
/// <param name="setter">Sets the value of the property. May be null.</param>
public DirectProperty(
string name,
Func<TOwner, TValue> getter,
Action<TOwner, TValue> setter = null)
: base(name, typeof(TOwner))
{
Contract.Requires<ArgumentNullException>(getter != null);
Getter = getter;
Setter = setter;
}
/// <summary>
/// Initializes a new instance of the <see cref="PerspexProperty"/> class.
/// </summary>
/// <param name="source">The property to copy.</param>
/// <param name="getter">Gets the current value of the property.</param>
/// <param name="setter">Sets the value of the property. May be null.</param>
private DirectProperty(
PerspexProperty<TValue> source,
Func<TOwner, TValue> getter,
Action<TOwner, TValue> setter)
: base(source, typeof(TOwner))
{
Contract.Requires<ArgumentNullException>(getter != null);
Getter = getter;
Setter = setter;
}
/// <inheritdoc/>
public override bool IsDirect => true;
/// <inheritdoc/>
public override bool IsReadOnly => Setter == null;
/// <summary>
/// Gets the getter function.
/// </summary>
public Func<TOwner, TValue> Getter { get; }
/// <summary>
/// Gets the setter function.
/// </summary>
public Action<TOwner, TValue> Setter { get; }
/// <summary>
/// Registers the direct property on another type.
/// </summary>
/// <typeparam name="TNewOwner">The type of the additional owner.</typeparam>
/// <returns>The property.</returns>
public DirectProperty<TNewOwner, TValue> AddOwner<TNewOwner>(
Func<TNewOwner, TValue> getter,
Action<TNewOwner, TValue> setter = null)
where TNewOwner : PerspexObject
{
var result = new DirectProperty<TNewOwner, TValue>(
this,
getter,
setter);
PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
return result;
}
/// <inheritdoc/>
object IDirectPropertyAccessor.GetValue(IPerspexObject instance)
{
return Getter((TOwner)instance);
}
/// <inheritdoc/>
void IDirectPropertyAccessor.SetValue(IPerspexObject instance, object value)
{
if (Setter == null)
{
throw new ArgumentException($"The property {Name} is readonly.");
}
Setter((TOwner)instance, (TValue)value);
}
}
}

31
src/Perspex.Base/IDirectPropertyAccessor.cs

@ -0,0 +1,31 @@
// 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>
/// Provides a runtime interface for getting and setting
/// <see cref="DirectProperty{TOwner, TValue}"/> values.
/// </summary>
internal interface IDirectPropertyAccessor
{
/// <summary>
/// Gets a value indicating whether the property is read-only.
/// </summary>
bool IsReadOnly { get; }
/// <summary>
/// Gets the value of the property on the instance.
/// </summary>
/// <param name="instance">The instance.</param>
/// <returns>The property value.</returns>
object GetValue(IPerspexObject instance);
/// <summary>
/// Sets the value of the property on the instance.
/// </summary>
/// <param name="instance">The instance.</param>
/// <param name="value">The value.</param>
void SetValue(IPerspexObject instance, object value);
}
}

31
src/Perspex.Base/IStyledPropertyAccessor.cs

@ -0,0 +1,31 @@
// 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;
namespace Perspex
{
/// <summary>
/// Provides a runtime interface for interfacing with <see cref="StyledProperty{TValue}"/>.
/// </summary>
internal interface IStyledPropertyAccessor
{
/// <summary>
/// Gets the default value for the property for the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// The default value.
/// </returns>
object GetDefaultValue(Type type);
/// <summary>
/// Gets a validation function for the property on the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// The validation function, or null if no validation function exists.
/// </returns>
Func<IPerspexObject, object, object> GetValidationFunc(Type type);
}
}

6
src/Perspex.Base/Perspex.Base.csproj

@ -48,12 +48,18 @@
<Compile Include="Collections\PerspexDictionary.cs" />
<Compile Include="Data\BindingMode.cs" />
<Compile Include="Diagnostics\PerspexObjectExtensions.cs" />
<Compile Include="AttachedProperty.cs" />
<Compile Include="IStyledPropertyAccessor.cs" />
<Compile Include="IDirectPropertyAccessor.cs" />
<Compile Include="DirectProperty.cs" />
<Compile Include="IPerspexObject.cs" />
<Compile Include="Metadata\ContentAttribute.cs" />
<Compile Include="PerspexDisposable.cs" />
<Compile Include="PerspexLocator.cs" />
<Compile Include="Metadata\XmlnsDefinitionAttribute.cs" />
<Compile Include="PerspexObjectExtensions.cs" />
<Compile Include="StyledProperty.cs" />
<Compile Include="StyledPropertyBase.cs" />
<Compile Include="PerspexPropertyRegistry.cs" />
<Compile Include="PerspexProperty`1.cs" />
<Compile Include="Platform\IAssetLoader.cs" />

85
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 ?
property.Getter(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) :
//// property.GetDefaultValue(GetType());
//// var e = new PerspexPropertyChangedEventArgs(
//// this,
//// property,
//// PerspexProperty.UnsetValue,
//// value,
//// BindingPriority.Unset);
//// property.NotifyInitialized(e);
////}
}
/// <summary>
@ -229,7 +229,7 @@ namespace Perspex
if (property.IsDirect)
{
return GetRegistered(property).Getter(this);
return ((IDirectPropertyAccessor)GetRegistered(property)).GetValue(this);
}
else
{
@ -267,7 +267,7 @@ namespace Perspex
if (property.IsDirect)
{
return ((PerspexProperty<T>)GetRegistered(property)).Getter(this);
return (T)((IDirectPropertyAccessor)GetRegistered(property)).GetValue(this);
}
else
{
@ -310,15 +310,9 @@ namespace Perspex
if (property.IsDirect)
{
property = GetRegistered(property);
if (property.Setter == null)
{
throw new ArgumentException($"The property {property.Name} is readonly.");
}
var accessor = (IDirectPropertyAccessor)GetRegistered(property);
LogPropertySet(property, value, priority);
property.Setter(this, UnsetToDefault(value, property));
accessor.SetValue(this, UnsetToDefault(value, property));
}
else
{
@ -368,23 +362,8 @@ namespace Perspex
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess();
if (property.IsDirect)
{
property = (PerspexProperty<T>)GetRegistered(property);
if (property.Setter == null)
{
throw new ArgumentException($"The property {property.Name} is readonly.");
}
LogPropertySet(property, value, priority);
property.Setter(this, value);
}
else
{
SetValue((PerspexProperty)property, value, priority);
}
SetValue((PerspexProperty)property, value, priority);
}
/// <summary>
@ -406,9 +385,7 @@ namespace Perspex
if (property.IsDirect)
{
property = GetRegistered(property);
if (property.Setter == null)
if (property.IsReadOnly)
{
throw new ArgumentException($"The property {property.Name} is readonly.");
}
@ -463,22 +440,8 @@ namespace Perspex
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess();
if (property.IsDirect)
{
property = (PerspexProperty<T>)GetRegistered(property);
if (property.Setter == null)
{
throw new ArgumentException($"The property {property.Name} is readonly.");
}
return source.Subscribe(x => SetValue(property, x));
}
else
{
return Bind((PerspexProperty)property, source.Select(x => (object)x), priority);
}
return Bind((PerspexProperty)property, source.Select(x => (object)x), priority);
}
/// <summary>
@ -621,7 +584,7 @@ namespace Perspex
/// <returns>The <see cref="PriorityValue"/>.</returns>
private PriorityValue CreatePriorityValue(PerspexProperty property)
{
Func<PerspexObject, object, object> validate = property.GetValidationFunc(GetType());
var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(GetType());
Func<object, object> validate2 = null;
if (validate != null)
@ -673,7 +636,7 @@ namespace Perspex
}
else
{
return property.GetDefaultValue(GetType());
return ((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
}
}

405
src/Perspex.Base/PerspexProperty.cs

@ -2,21 +2,15 @@
// 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.Linq;
using System.Reactive.Subjects;
using System.Reflection;
using Perspex.Data;
using Perspex.Utilities;
namespace Perspex
{
/// <summary>
/// A perspex property.
/// Base class for perspex property metadata.
/// </summary>
/// <remarks>
/// This class is analogous to DependencyProperty in WPF.
/// </remarks>
public class PerspexProperty : IEquatable<PerspexProperty>
{
/// <summary>
@ -29,16 +23,6 @@ namespace Perspex
/// </summary>
private static int s_nextId = 1;
/// <summary>
/// The default value provided when the property was first registered.
/// </summary>
private readonly object _defaultValue;
/// <summary>
/// The overridden default values for the property, by type.
/// </summary>
private readonly Dictionary<Type, object> _defaultValues;
/// <summary>
/// Observable fired when this property changes on any <see cref="PerspexObject"/>.
/// </summary>
@ -49,11 +33,6 @@ namespace Perspex
/// </summary>
private readonly Subject<PerspexPropertyChangedEventArgs> _changed;
/// <summary>
/// The validation functions for the property, by type.
/// </summary>
private readonly Dictionary<Type, Func<PerspexObject, object, object>> _validation;
/// <summary>
/// Gets the ID of the property.
/// </summary>
@ -65,26 +44,18 @@ 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="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="isAttached">Whether the property is an attached property.</param>
public PerspexProperty(
protected PerspexProperty(
string name,
Type valueType,
Type ownerType,
object defaultValue,
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.Default,
Func<PerspexObject, object, object> validate = null,
Action<PerspexObject, bool> notifying = null,
bool isAttached = false)
Action<PerspexObject, bool> notifying = null)
{
Contract.Requires<ArgumentNullException>(name != null);
Contract.Requires<ArgumentNullException>(valueType != null);
@ -95,64 +66,15 @@ namespace Perspex
throw new ArgumentException("'name' may not contain periods.");
}
_defaultValues = new Dictionary<Type, object>();
_initialized = new Subject<PerspexPropertyChangedEventArgs>();
_changed = new Subject<PerspexPropertyChangedEventArgs>();
_validation = new Dictionary<Type, Func<PerspexObject, object, object>>();
Name = name;
PropertyType = valueType;
OwnerType = ownerType;
_defaultValue = defaultValue;
Inherits = inherits;
DefaultBindingMode = defaultBindingMode;
IsAttached = isAttached;
Notifying = notifying;
_id = s_nextId++;
if (validate != null)
{
_validation.Add(ownerType, validate);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="PerspexProperty"/> class.
/// </summary>
/// <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="getter">Gets the current value of the property.</param>
/// <param name="setter">Sets the value of the property.</param>
public PerspexProperty(
string name,
Type valueType,
Type ownerType,
Func<PerspexObject, object> getter,
Action<PerspexObject, object> setter)
{
Contract.Requires<ArgumentNullException>(name != null);
Contract.Requires<ArgumentNullException>(valueType != null);
Contract.Requires<ArgumentNullException>(ownerType != null);
Contract.Requires<ArgumentNullException>(getter != null);
if (name.Contains("."))
{
throw new ArgumentException("'name' may not contain periods.");
}
_defaultValues = new Dictionary<Type, object>();
_initialized = new Subject<PerspexPropertyChangedEventArgs>();
_changed = new Subject<PerspexPropertyChangedEventArgs>();
_validation = new Dictionary<Type, Func<PerspexObject, object, object>>();
Name = name;
PropertyType = valueType;
OwnerType = ownerType;
Getter = getter;
Setter = setter;
IsDirect = true;
_id = s_nextId++;
}
/// <summary>
@ -171,117 +93,62 @@ namespace Perspex
"This method cannot be called on direct PerspexProperties.");
}
_defaultValues = source._defaultValues;
_initialized = source._initialized;
_changed = source._changed;
_validation = source._validation;
Name = source.Name;
PropertyType = source.PropertyType;
OwnerType = ownerType;
_defaultValue = source._defaultValue;
Inherits = source.Inherits;
DefaultBindingMode = source.DefaultBindingMode;
IsAttached = false;
Notifying = Notifying;
_validation = source._validation;
_id = source._id;
}
/// <summary>
/// Initializes a new instance of the <see cref="PerspexProperty"/> class.
/// Gets the name of the property.
/// </summary>
/// <param name="source">The direct property to copy.</param>
/// <param name="ownerType">The new owner type.</param>
/// <param name="getter">A new getter.</param>
/// <param name="setter">A new setter.</param>
protected PerspexProperty(
PerspexProperty source,
Type ownerType,
Func<PerspexObject, object> getter,
Action<PerspexObject, object> setter)
{
Contract.Requires<ArgumentNullException>(source != null);
Contract.Requires<ArgumentNullException>(ownerType != null);
Contract.Requires<ArgumentNullException>(getter != null);
if (!source.IsDirect)
{
throw new InvalidOperationException(
"This method can only be called on direct PerspexProperties.");
}
_defaultValues = source._defaultValues;
_initialized = source._initialized;
_changed = source._changed;
_validation = source._validation;
Name = source.Name;
PropertyType = source.PropertyType;
OwnerType = ownerType;
Getter = getter;
Setter = setter;
IsDirect = true;
_id = source._id;
}
public string Name { get; }
/// <summary>
/// Gets the name of the property.
/// Gets the full name of the property, wich includes the owner type in the case of
/// attached properties.
/// </summary>
/// <value>
/// The name of the property.
/// </value>
public string Name { get; }
public virtual string FullName => Name;
/// <summary>
/// Gets the type of the property's value.
/// </summary>
/// <value>
/// The type of the property's value.
/// </value>
public Type PropertyType { get; }
/// <summary>
/// Gets the type of the class that registers the property.
/// Gets the type of the class that registered the property.
/// </summary>
/// <value>
/// The type of the class that registers the property.
/// </value>
public Type OwnerType { get; }
/// <summary>
/// Gets a value indicating whether the property inherits its value.
/// </summary>
/// <value>
/// A value indicating whether the property inherits its value.
/// </value>
public bool Inherits { get; }
public virtual bool Inherits => false;
/// <summary>
/// Gets the default binding mode for the property.
/// </summary>
/// <value>
/// The default binding mode for the property.
/// </value>
public BindingMode DefaultBindingMode { get; }
/// <summary>
/// Gets a value indicating whether this is an attached property.
/// </summary>
/// <value>
/// A value indicating whether this is an attached property.
/// </value>
public bool IsAttached { get; }
public virtual bool IsAttached => false;
/// <summary>
/// Gets a value indicating whether this is a direct property.
/// </summary>
public bool IsDirect { get; }
public virtual bool IsDirect => false;
/// <summary>
/// Gets a value indicating whether this is a readonly property.
/// </summary>
public bool IsReadOnly => IsDirect && Setter == null;
public virtual bool IsReadOnly => false;
/// <summary>
/// Gets an observable that is fired when this property is initialized on a
@ -349,16 +216,6 @@ namespace Perspex
};
}
/// <summary>
/// Gets the getter function for direct properties.
/// </summary>
internal Func<PerspexObject, object> Getter { get; }
/// <summary>
/// Gets the etter function for direct properties.
/// </summary>
internal Action<PerspexObject, object> Setter { get; }
/// <summary>
/// Tests two <see cref="PerspexProperty"/>s for equality.
/// </summary>
@ -407,55 +264,26 @@ namespace Perspex
/// object; the bool argument will be true before and false afterwards. This callback is
/// intended to support IsDataContextChanging.
/// </param>
/// <returns>A <see cref="PerspexProperty{TValue}"/></returns>
public static PerspexProperty<TValue> Register<TOwner, TValue>(
/// <returns>A <see cref="StyledProperty{TValue}"/></returns>
public static StyledProperty<TValue> Register<TOwner, TValue>(
string name,
TValue defaultValue = default(TValue),
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay,
Func<TOwner, TValue, TValue> validate = null,
Action<PerspexObject, bool> notifying = null)
where TOwner : PerspexObject
Action<IPerspexObject, bool> notifying = null)
where TOwner : IPerspexObject
{
Contract.Requires<ArgumentNullException>(name != null);
PerspexProperty<TValue> result = new PerspexProperty<TValue>(
var result = new StyledProperty<TValue>(
name,
typeof(TOwner),
defaultValue,
inherits,
defaultBindingMode,
Cast(validate),
notifying,
false);
PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
return result;
}
/// <summary>
/// Registers a direct <see cref="PerspexProperty"/>.
/// </summary>
/// <typeparam name="TOwner">The type of the class that is registering the property.</typeparam>
/// <typeparam name="TValue">The type of the property's value.</typeparam>
/// <param name="name">The name of the property.</param>
/// <param name="getter">Gets the current value of the property.</param>
/// <param name="setter">Sets the value of the property.</param>
/// <returns>A <see cref="PerspexProperty{TValue}"/></returns>
public static PerspexProperty<TValue> RegisterDirect<TOwner, TValue>(
string name,
Func<TOwner, TValue> getter,
Action<TOwner, TValue> setter = null)
where TOwner : PerspexObject
{
Contract.Requires<ArgumentNullException>(name != null);
PerspexProperty<TValue> result = new PerspexProperty<TValue>(
name,
typeof(TOwner),
Cast(getter),
Cast(setter));
notifying);
PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
@ -474,24 +302,23 @@ 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<TOwner, THost, TValue>(
public static AttachedProperty<TValue> RegisterAttached<TOwner, THost, TValue>(
string name,
TValue defaultValue = default(TValue),
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay,
Func<PerspexObject, TValue, TValue> validate = null)
Func<THost, TValue, TValue> validate = null)
where THost : IPerspexObject
{
Contract.Requires<ArgumentNullException>(name != null);
PerspexProperty<TValue> result = new PerspexProperty<TValue>(
var result = new AttachedProperty<TValue>(
name,
typeof(TOwner),
defaultValue,
inherits,
defaultBindingMode,
validate,
null,
true);
Cast(validate));
PerspexPropertyRegistry.Instance.Register(typeof(THost), result);
@ -516,25 +343,46 @@ namespace Perspex
TValue defaultValue = default(TValue),
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay,
Func<PerspexObject, TValue, TValue> validate = null)
Func<THost, TValue, TValue> validate = null)
where THost : IPerspexObject
{
Contract.Requires<ArgumentNullException>(name != null);
PerspexProperty<TValue> result = new PerspexProperty<TValue>(
var result = new AttachedProperty<TValue>(
name,
ownerType,
defaultValue,
inherits,
defaultBindingMode,
validate,
null,
true);
Cast(validate));
PerspexPropertyRegistry.Instance.Register(typeof(THost), result);
return result;
}
/// <summary>
/// Registers a direct <see cref="PerspexProperty"/>.
/// </summary>
/// <typeparam name="TOwner">The type of the class that is registering the property.</typeparam>
/// <typeparam name="TValue">The type of the property's value.</typeparam>
/// <param name="name">The name of the property.</param>
/// <param name="getter">Gets the current value of the property.</param>
/// <param name="setter">Sets the value of the property.</param>
/// <returns>A <see cref="PerspexProperty{TValue}"/></returns>
public static DirectProperty<TOwner, TValue> RegisterDirect<TOwner, TValue>(
string name,
Func<TOwner, TValue> getter,
Action<TOwner, TValue> setter = null)
where TOwner : IPerspexObject
{
Contract.Requires<ArgumentNullException>(name != null);
var result = new DirectProperty<TOwner, TValue>(name, getter, setter);
PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
return result;
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
@ -570,56 +418,6 @@ namespace Perspex
};
}
/// <summary>
/// Gets the default value for the property on the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>The default value.</returns>
public object GetDefaultValue(Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
while (type != null)
{
object result;
if (_defaultValues.TryGetValue(type, out result))
{
return result;
}
type = type.GetTypeInfo().BaseType;
}
return _defaultValue;
}
/// <summary>
/// Gets the validation function for the property on the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// The validation function, or null if no validation function registered for this type.
/// </returns>
public Func<PerspexObject, object, object> GetValidationFunc(Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
while (type != null)
{
Func<PerspexObject, object, object> result;
if (_validation.TryGetValue(type, out result))
{
return result;
}
type = type.GetTypeInfo().BaseType;
}
return null;
}
/// <summary>
/// Checks whether the <paramref name="value"/> is valid for the property.
/// </summary>
@ -630,59 +428,6 @@ namespace Perspex
return TypeUtilities.TryCast(PropertyType, value, out value);
}
/// <summary>
/// Overrides the default value for the property on the specified type.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <param name="defaultValue">The default value.</param>
public void OverrideDefaultValue<T>(object defaultValue)
{
OverrideDefaultValue(typeof(T), defaultValue);
}
/// <summary>
/// Overrides the default value for the property on the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="defaultValue">The default value.</param>
public void OverrideDefaultValue(Type type, object defaultValue)
{
Contract.Requires<ArgumentNullException>(type != null);
if (!TypeUtilities.TryCast(PropertyType, defaultValue, out defaultValue))
{
throw new InvalidOperationException(string.Format(
"Invalid value for Property '{0}': {1} ({2})",
Name,
defaultValue,
defaultValue.GetType().FullName));
}
if (_defaultValues.ContainsKey(type))
{
throw new InvalidOperationException("Default value is already set for this property.");
}
_defaultValues.Add(type, defaultValue);
}
/// <summary>
/// Overrides the validation function for the property on the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="validation">The validation function.</param>
public void OverrideValidation(Type type, Func<PerspexObject, object, object> validation)
{
Contract.Requires<ArgumentNullException>(type != null);
if (_validation.ContainsKey(type))
{
throw new InvalidOperationException("Validation is already set for this property.");
}
_validation.Add(type, validation);
}
/// <summary>
/// Gets the string representation of the property.
/// </summary>
@ -711,45 +456,24 @@ namespace Perspex
}
/// <summary>
/// Casts a getter function accepting a typed owner to one accepting a
/// <see cref="PerspexObject"/>.
/// Casts a validation function accepting a typed owner to one accepting an
/// <see cref="IPerspexObject"/>.
/// </summary>
/// <typeparam name="TOwner">The owner type.</typeparam>
/// <typeparam name="TValue">The property value type.</typeparam>
/// <param name="f">The typed function.</param>
/// <returns>The untyped function.</returns>
private static Func<PerspexObject, TValue> Cast<TOwner, TValue>(Func<TOwner, TValue> f)
where TOwner : PerspexObject
protected static Func<IPerspexObject, TValue, TValue> Cast<TOwner, TValue>(Func<TOwner, TValue, TValue> f)
where TOwner : IPerspexObject
{
return (f != null) ? o => f((TOwner)o) : (Func<PerspexObject, TValue >)null;
}
/// <summary>
/// Casts a setter action accepting a typed owner to one accepting a
/// <see cref="PerspexObject"/>.
/// </summary>
/// <typeparam name="TOwner">The owner type.</typeparam>
/// <typeparam name="TValue">The property value type.</typeparam>
/// <param name="f">The typed action.</param>
/// <returns>The untyped action.</returns>
private static Action<PerspexObject, TValue> Cast<TOwner, TValue>(Action<TOwner, TValue> f)
where TOwner : PerspexObject
{
return f != null ? (o, v) => f((TOwner)o, v) : (Action<PerspexObject, TValue>)null;
}
/// <summary>
/// Casts a validation function accepting a typed owner to one accepting a
/// <see cref="PerspexObject"/>.
/// </summary>
/// <typeparam name="TOwner">The owner type.</typeparam>
/// <typeparam name="TValue">The property value type.</typeparam>
/// <param name="f">The typed function.</param>
/// <returns>The untyped function.</returns>
private static Func<PerspexObject, TValue, TValue> Cast<TOwner, TValue>(Func<TOwner, TValue, TValue> f)
where TOwner : PerspexObject
{
return f != null ? (o, v) => f((TOwner)o, v) : (Func<PerspexObject, TValue, TValue>)null;
if (f == null)
{
return null;
}
else
{
return (o, v) => f((TOwner)o, v);
}
}
/// <summary>
@ -761,10 +485,7 @@ namespace Perspex
/// Returns the string representation of the <see cref="UnsetValue"/>.
/// </summary>
/// <returns>The string "(unset)".</returns>
public override string ToString()
{
return "(unset)";
}
public override string ToString() => "(unset)";
}
}
}

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

@ -17,217 +17,34 @@ 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="isAttached">Whether the property is an attached property.</param>
public PerspexProperty(
protected PerspexProperty(
string name,
Type ownerType,
TValue defaultValue = default(TValue),
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.Default,
Func<PerspexObject, TValue, TValue> validate = null,
Action<PerspexObject, bool> notifying = null,
bool isAttached = false)
Action<PerspexObject, bool> notifying = null)
: base(
name,
typeof(TValue),
ownerType,
defaultValue,
inherits,
defaultBindingMode,
Cast(validate),
notifying,
isAttached)
notifying)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PerspexProperty{TValue}"/> 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="getter">Gets the current value of the property.</param>
/// <param name="setter">Sets the value of the property.</param>
public PerspexProperty(
string name,
Type ownerType,
Func<PerspexObject, TValue> getter,
Action<PerspexObject, TValue> setter)
: base(name, typeof(TValue), ownerType, CastParamReturn(getter), CastParams(setter))
{
Getter = getter;
Setter = setter;
}
/// <summary>
/// Initializes a new instance of the <see cref="PerspexProperty"/> class.
/// </summary>
/// <param name="source">The property to copy.</param>
/// <param name="ownerType">The new owner type.</param>
private PerspexProperty(PerspexProperty source, Type ownerType)
protected PerspexProperty(PerspexProperty source, Type ownerType)
: base(source, ownerType)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PerspexProperty"/> class.
/// </summary>
/// <param name="source">The direct property to copy.</param>
/// <param name="ownerType">The new owner type.</param>
/// <param name="getter">A new getter.</param>
/// <param name="setter">A new setter.</param>
private PerspexProperty(
PerspexProperty source,
Type ownerType,
Func<PerspexObject, TValue> getter,
Action<PerspexObject, TValue> setter)
: base(source, ownerType, CastParamReturn(getter), CastParams(setter))
{
Getter = getter;
Setter = setter;
}
/// <summary>
/// Gets the getter function for direct properties.
/// </summary>
internal new Func<PerspexObject, TValue> Getter { get; }
/// <summary>
/// Gets the etter function for direct properties.
/// </summary>
internal new Action<PerspexObject, TValue> Setter { get; }
/// <summary>
/// Registers the property on another type.
/// </summary>
/// <typeparam name="TOwner">The type of the additional owner.</typeparam>
/// <returns>The property.</returns>
public PerspexProperty<TValue> AddOwner<TOwner>() where TOwner : PerspexObject
{
if (IsDirect)
{
throw new InvalidOperationException(
"You must provide a new getter and setter when calling AddOwner on a direct PerspexProperty.");
}
var result = new PerspexProperty<TValue>(this, typeof(TOwner));
PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
return result;
}
/// <summary>
/// Registers the direct property on another type.
/// </summary>
/// <typeparam name="TOwner">The type of the additional owner.</typeparam>
/// <returns>The property.</returns>
public PerspexProperty<TValue> AddOwner<TOwner>(
Func<TOwner, TValue> getter,
Action<TOwner, TValue> setter = null)
where TOwner : PerspexObject
{
if (!IsDirect)
{
throw new InvalidOperationException(
"This overload of AddOwner is for direct PerspexProperties.");
}
var result = new PerspexProperty<TValue>(
this,
typeof(TOwner),
CastReturn(getter),
CastParam1(setter));
PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
return result;
}
/// <summary>
/// Gets the default value for the property on the specified type.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <returns>The default value.</returns>
public TValue GetDefaultValue<T>()
{
return (TValue)GetDefaultValue(typeof(T));
}
/// <summary>
/// Overrides the validation function for the property on the specified type.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <param name="validation">The validation function.</param>
public void OverrideValidation<T>(Func<T, TValue, TValue> validation) where T : PerspexObject
{
var f = validation != null ?
(o, v) => validation((T)o, (TValue)v) :
(Func<PerspexObject, object, object>)null;
OverrideValidation(typeof(T), f);
}
/// <summary>
/// Casts a typed getter function to an untyped.
/// </summary>
/// <typeparam name="TOwner">The owner type.</typeparam>
/// <param name="f">The typed function.</param>
/// <returns>The untyped function.</returns>
private static Func<PerspexObject, object> CastParamReturn<TOwner>(Func<TOwner, TValue> f)
where TOwner : PerspexObject
{
return (f != null) ? o => f((TOwner)o) : (Func<PerspexObject, object>)null;
}
/// <summary>
/// Casts a typed getter function to an untyped.
/// </summary>
/// <typeparam name="TOwner">The owner type.</typeparam>
/// <param name="f">The typed function.</param>
/// <returns>The untyped function.</returns>
private static Func<PerspexObject, TValue> CastReturn<TOwner>(Func<TOwner, TValue> f)
where TOwner : PerspexObject
{
return (f != null) ? o => f((TOwner)o) : (Func<PerspexObject, TValue>)null;
}
/// <summary>
/// Casts a typed setter function to an untyped.
/// </summary>
/// <typeparam name="TOwner">The owner type.</typeparam>
/// <param name="f">The typed function.</param>
/// <returns>The untyped function.</returns>
private static Action<PerspexObject, object> CastParams<TOwner>(Action<TOwner, TValue> f)
where TOwner : PerspexObject
{
return (f != null) ? (o, v) => f((TOwner)o, (TValue)v) : (Action<PerspexObject, object>)null;
}
/// <summary>
/// Casts a typed setter function to an untyped.
/// </summary>
/// <typeparam name="TOwner">The owner type.</typeparam>
/// <param name="f">The typed function.</param>
/// <returns>The untyped function.</returns>
private static Action<PerspexObject, TValue> CastParam1<TOwner>(Action<TOwner, TValue> f)
where TOwner : PerspexObject
{
return (f != null) ? (o, v) => f((TOwner)o, v) : (Action<PerspexObject, TValue>)null;
}
/// <summary>
/// Casts a typed validation function to an untyped.
/// </summary>
/// <param name="f">The typed validation function.</param>
/// <returns>The untyped validation function.</returns>
private static Func<PerspexObject, object, object> Cast(Func<PerspexObject, TValue, TValue> f)
{
return f != null ? (o, v) => f(o, (TValue)v) : (Func<PerspexObject, object, object>)null;
}
}
}

40
src/Perspex.Base/StyledProperty.cs

@ -0,0 +1,40 @@
// 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>
/// A styled perspex property.
/// </summary>
public class StyledProperty<TValue> : StyledPropertyBase<TValue>
{
/// <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>
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)
{
}
}
}

204
src/Perspex.Base/StyledPropertyBase.cs

@ -0,0 +1,204 @@
// 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.Generic;
using System.Reflection;
using Perspex.Data;
namespace Perspex
{
/// <summary>
/// Base class for styled properties.
/// </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>
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)
{
Contract.Requires<ArgumentNullException>(name != null);
Contract.Requires<ArgumentNullException>(ownerType != null);
if (name.Contains("."))
{
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>
/// Gets a value indicating whether the property inherits its value.
/// </summary>
/// <value>
/// A value indicating whether the property inherits its value.
/// </value>
public override bool Inherits => _inherits;
/// <summary>
/// Gets the default value for the property on the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>The default value.</returns>
public TValue GetDefaultValue(Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
while (type != null)
{
TValue result;
if (_defaultValues.TryGetValue(type, out result))
{
return result;
}
type = type.GetTypeInfo().BaseType;
}
return _defaultValue;
}
/// <summary>
/// Gets the validation function for the property on the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// The validation function, or null if no validation function registered for this type.
/// </returns>
public Func<IPerspexObject, TValue, TValue> GetValidationFunc(Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
while (type != null)
{
Func<IPerspexObject, TValue, TValue> result;
if (_validation.TryGetValue(type, out result))
{
return result;
}
type = type.GetTypeInfo().BaseType;
}
return null;
}
/// <summary>
/// Overrides the default value for the property on the specified type.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <param name="defaultValue">The default value.</param>
public void OverrideDefaultValue<T>(TValue defaultValue) where T : IPerspexObject
{
OverrideDefaultValue(typeof(T), defaultValue);
}
/// <summary>
/// Overrides the default value for the property on the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <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);
}
/// <summary>
/// Overrides the validation function for the property on the specified type.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <param name="validation">The validation function.</param>
public void OverrideValidation<T>(Func<T, TValue, TValue> validation)
where T : IPerspexObject
{
var type = typeof(T);
if (_validation.ContainsKey(type))
{
throw new InvalidOperationException("Validation is already set for this property.");
}
_validation.Add(type, Cast(validation));
}
/// <summary>
/// Overrides the validation function for the property on the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="validation">The validation function.</param>
public void OverrideValidation(Type type, Func<IPerspexObject, TValue, TValue> validation)
{
Contract.Requires<ArgumentNullException>(type != null);
if (_validation.ContainsKey(type))
{
throw new InvalidOperationException("Validation is already set for this property.");
}
_validation.Add(type, validation);
}
/// <summary>
/// Gets the string representation of the property.
/// </summary>
/// <returns>The property's string representation.</returns>
public override string ToString()
{
return Name;
}
/// <inheritdoc/>
Func<IPerspexObject, object, object> IStyledPropertyAccessor.GetValidationFunc(Type type)
{
var typed = GetValidationFunc(type);
return (o, v) => typed(o, (TValue)v);
}
/// <inheritdoc/>
object IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultValue(type);
}
}

28
src/Perspex.Controls/Grid.cs

@ -17,7 +17,9 @@ namespace Perspex.Controls
/// Defines the Column attached property.
/// </summary>
public static readonly PerspexProperty<int> ColumnProperty =
PerspexProperty.RegisterAttached<Grid, Control, int>("Column");
PerspexProperty.RegisterAttached<Grid, Control, int>(
"Column",
validate: ValidateColumn);
/// <summary>
/// Defines the ColumnSpan attached property.
@ -29,7 +31,9 @@ namespace Perspex.Controls
/// Defines the Row attached property.
/// </summary>
public static readonly PerspexProperty<int> RowProperty =
PerspexProperty.RegisterAttached<Grid, Control, int>("Row");
PerspexProperty.RegisterAttached<Grid, Control, int>(
"Row",
validate: ValidateRow);
/// <summary>
/// Defines the RowSpan attached property.
@ -578,6 +582,26 @@ namespace Perspex.Controls
}
}
private static int ValidateColumn(PerspexObject o, int value)
{
if (value < 0)
{
throw new ArgumentException("Invalid Grid.Column value.");
}
return value;
}
private static int ValidateRow(PerspexObject o, int value)
{
if (value < 0)
{
throw new ArgumentException("Invalid Grid.Row value.");
}
return value;
}
private void CreateMatrices(int rowCount, int colCount)
{
if (_rowMatrix == null || _colMatrix == null ||

6
tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs

@ -341,13 +341,13 @@ namespace Perspex.Base.UnitTests
private class Class1 : PerspexObject
{
public static readonly PerspexProperty<string> FooProperty =
public static readonly DirectProperty<Class1, string> FooProperty =
PerspexProperty.RegisterDirect<Class1, string>("Foo", o => o.Foo, (o, v) => o.Foo = v);
public static readonly PerspexProperty<string> BarProperty =
public static readonly DirectProperty<Class1, string> BarProperty =
PerspexProperty.RegisterDirect<Class1, string>("Bar", o => o.Bar);
public static readonly PerspexProperty<int> BazProperty =
public static readonly DirectProperty<Class1, int> BazProperty =
PerspexProperty.RegisterDirect<Class1, int>("Bar", o => o.Baz, (o,v) => o.Baz = v);
private string _foo = "initial";

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

@ -55,10 +55,10 @@ namespace Perspex.Base.UnitTests
private class Class1 : PerspexObject
{
public static readonly PerspexProperty<string> FooProperty =
public static readonly StyledProperty<string> FooProperty =
PerspexProperty.Register<Class1, string>("Foo", "foodefault");
public static readonly PerspexProperty<string> BazProperty =
public static readonly StyledProperty<string> BazProperty =
PerspexProperty.Register<Class1, string>("Baz", "bazdefault", true);
}

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

@ -77,10 +77,10 @@ namespace Perspex.Base.UnitTests
private class Class1 : PerspexObject
{
public static readonly PerspexProperty<string> FooProperty =
public static readonly StyledProperty<string> FooProperty =
PerspexProperty.Register<Class1, string>("Foo", "foodefault");
public static readonly PerspexProperty<string> BazProperty =
public static readonly StyledProperty<string> BazProperty =
PerspexProperty.Register<Class1, string>("Baz", "bazdefault", true);
}

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

@ -71,7 +71,7 @@ namespace Perspex.Base.UnitTests
private class Class1 : PerspexObject
{
public static readonly PerspexProperty<int> QuxProperty =
public static readonly StyledProperty<int> QuxProperty =
PerspexProperty.Register<Class1, int>("Qux", validate: Validate);
public Class1()
@ -97,7 +97,7 @@ namespace Perspex.Base.UnitTests
private class Class2 : PerspexObject
{
public static readonly PerspexProperty<int> QuxProperty =
public static readonly StyledProperty<int> QuxProperty =
Class1.QuxProperty.AddOwner<Class2>();
static Class2()

Loading…
Cancel
Save