Browse Source

Merge branch 'direct-perspexproperties'

pull/227/head
Steven Kirk 11 years ago
parent
commit
bd4d76796f
  1. 4
      src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs
  2. 47
      src/Perspex.Base/BindingPriority.cs
  3. 1
      src/Perspex.Base/Perspex.Base.csproj
  4. 434
      src/Perspex.Base/PerspexObject.cs
  5. 221
      src/Perspex.Base/PerspexProperty.cs
  6. 123
      src/Perspex.Base/PerspexProperty`1.cs
  7. 4
      src/Perspex.Base/PriorityValue.cs
  8. 2
      src/Perspex.Base/Utilities/TypeUtilities.cs
  9. 14
      src/Perspex.Controls/Control.cs
  10. 10
      src/Perspex.Controls/DropDown.cs
  11. 51
      src/Perspex.Controls/ItemsControl.cs
  12. 10
      src/Perspex.Controls/Menu.cs
  13. 17
      src/Perspex.Controls/Presenters/ContentPresenter.cs
  14. 19
      src/Perspex.Controls/Presenters/DeckPresenter.cs
  15. 8
      src/Perspex.Controls/Presenters/ItemsPresenter.cs
  16. 62
      src/Perspex.Controls/Primitives/SelectingItemsControl.cs
  17. 36
      src/Perspex.Controls/TopLevel.cs
  18. 15
      src/Perspex.Input/InputElement.cs
  19. 2
      src/Perspex.Input/InputExtensions.cs
  20. 18
      src/Perspex.Interactivity/Interactive.cs
  21. 14
      src/Perspex.Interactivity/RoutedEvent.cs
  22. 21
      src/Perspex.SceneGraph/Visual.cs
  23. 8
      src/Perspex.SceneGraph/VisualTree/VisualExtensions.cs
  24. 2
      src/Perspex.Styling/LogicalTree/LogicalExtensions.cs
  25. 2
      src/Perspex.Styling/Styling/StyleBinding.cs
  26. 1
      tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj
  27. 4
      tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs
  28. 332
      tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs
  29. 13
      tests/Perspex.Base.UnitTests/PerspexObjectTests_GetValue.cs
  30. 4
      tests/Perspex.Base.UnitTests/PerspexObjectTests_SetValue.cs
  31. 65
      tests/Perspex.Base.UnitTests/PerspexPropertyTests.cs
  32. 14
      tests/Perspex.Base.UnitTests/PriorityValueTests.cs
  33. 18
      tests/Perspex.Controls.UnitTests/ContentPresenterTests.cs
  34. 1
      tests/Perspex.Controls.UnitTests/Primitives/TabStripTests.cs
  35. 6
      tests/Perspex.Controls.UnitTests/Utils/HotKeyManagerTests.cs
  36. 6
      tests/Perspex.Markup.Xaml.UnitTests/Properties/AssemblyInfo.cs
  37. 16
      tests/Perspex.SceneGraph.UnitTests/VisualTests.cs

4
src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs

@ -24,8 +24,8 @@ namespace Perspex.Cairo.Media
TextAlignment textAlignment,
FontWeight fontWeight)
{
Contract.Requires<NullReferenceException>(context != null);
Contract.Requires<NullReferenceException> (text != null);
Contract.Requires<ArgumentNullException>(context != null);
Contract.Requires<ArgumentNullException> (text != null);
Layout = new Pango.Layout(context);
_text = text;
Layout.SetText(text);

47
src/Perspex.Base/BindingPriority.cs

@ -0,0 +1,47 @@
// 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>
/// The priority of a binding.
/// </summary>
public enum BindingPriority
{
/// <summary>
/// A value that comes from an animation.
/// </summary>
Animation = -1,
/// <summary>
/// A local value.
/// </summary>
LocalValue = 0,
/// <summary>
/// A triggered style binding.
/// </summary>
/// <remarks>
/// A style trigger is a selector such as .class which overrides a
/// <see cref="TemplatedParent"/> binding. In this way, a basic control can have
/// for example a Background from the templated parent which changes when the
/// control has the :pointerover class.
/// </remarks>
StyleTrigger,
/// <summary>
/// A binding to a property on the templated parent.
/// </summary>
TemplatedParent,
/// <summary>
/// A style binding.
/// </summary>
Style,
/// <summary>
/// The binding is uninitialized.
/// </summary>
Unset = int.MaxValue,
}
}

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

@ -51,6 +51,7 @@
<Compile Include="PerspexObjectExtensions.cs" />
<Compile Include="PerspexProperty`1.cs" />
<Compile Include="Platform\IPclPlatformWrapper.cs" />
<Compile Include="BindingPriority.cs" />
<Compile Include="PriorityBindingEntry.cs" />
<Compile Include="Collections\IPerspexList.cs" />
<Compile Include="Collections\PerspexList.cs" />

434
src/Perspex.Base/PerspexObject.cs

@ -15,48 +15,6 @@ using Serilog.Core.Enrichers;
namespace Perspex
{
/// <summary>
/// The priority of a binding.
/// </summary>
public enum BindingPriority
{
/// <summary>
/// A value that comes from an animation.
/// </summary>
Animation = -1,
/// <summary>
/// A local value.
/// </summary>
LocalValue = 0,
/// <summary>
/// A triggered style binding.
/// </summary>
/// <remarks>
/// A style trigger is a selector such as .class which overrides a
/// <see cref="TemplatedParent"/> binding. In this way, a basic control can have
/// for example a Background from the templated parent which changes when the
/// control has the :pointerover class.
/// </remarks>
StyleTrigger,
/// <summary>
/// A binding to a property on the templated parent.
/// </summary>
TemplatedParent,
/// <summary>
/// A style binding.
/// </summary>
Style,
/// <summary>
/// The binding is uninitialized.
/// </summary>
Unset = int.MaxValue,
}
/// <summary>
/// An object with <see cref="PerspexProperty"/> support.
/// </summary>
@ -112,11 +70,15 @@ namespace Perspex
foreach (var property in GetRegisteredProperties())
{
object value = property.IsDirect ?
property.Getter(this) :
property.GetDefaultValue(GetType());
var e = new PerspexPropertyChangedEventArgs(
this,
property,
PerspexProperty.UnsetValue,
property.GetDefaultValue(GetType()),
value,
BindingPriority.Unset);
property.NotifyInitialized(e);
@ -222,7 +184,7 @@ namespace Perspex
binding.Mode;
var sourceBinding = value as BindingDescriptor;
if (sourceBinding == null && mode != BindingMode.OneWay)
if (sourceBinding == null && mode > BindingMode.OneWay)
{
throw new InvalidOperationException("Can only bind OneWay to plain IObservable.");
}
@ -253,7 +215,7 @@ namespace Perspex
/// <returns>A collection of <see cref="PerspexProperty"/> definitions.</returns>
public static IEnumerable<PerspexProperty> GetRegisteredProperties(Type type)
{
Contract.Requires<NullReferenceException>(type != null);
Contract.Requires<ArgumentNullException>(type != null);
TypeInfo i = type.GetTypeInfo();
@ -301,8 +263,8 @@ namespace Perspex
/// </remarks>
public static void Register(Type type, PerspexProperty property)
{
Contract.Requires<NullReferenceException>(type != null);
Contract.Requires<NullReferenceException>(property != null);
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(property != null);
List<PerspexProperty> list;
@ -338,7 +300,7 @@ namespace Perspex
/// <param name="property">The property.</param>
public void ClearValue(PerspexProperty property)
{
Contract.Requires<NullReferenceException>(property != null);
Contract.Requires<ArgumentNullException>(property != null);
SetValue(property, PerspexProperty.UnsetValue);
}
@ -350,7 +312,7 @@ namespace Perspex
/// <returns>An observable.</returns>
public IObservable<object> GetObservable(PerspexProperty property)
{
Contract.Requires<NullReferenceException>(property != null);
Contract.Requires<ArgumentNullException>(property != null);
return new PerspexObservable<object>(
observer =>
@ -383,7 +345,7 @@ namespace Perspex
/// <returns>An observable.</returns>
public IObservable<T> GetObservable<T>(PerspexProperty<T> property)
{
Contract.Requires<NullReferenceException>(property != null);
Contract.Requires<ArgumentNullException>(property != null);
return GetObservable((PerspexProperty)property).Cast<T>();
}
@ -425,27 +387,34 @@ namespace Perspex
/// <returns>The value.</returns>
public object GetValue(PerspexProperty property)
{
Contract.Requires<NullReferenceException>(property != null);
Contract.Requires<ArgumentNullException>(property != null);
object result;
PriorityValue value;
if (_values.TryGetValue(property, out value))
if (property.IsDirect)
{
result = value.Value;
return GetRegistered(property).Getter(this);
}
else
{
result = PerspexProperty.UnsetValue;
}
object result = PerspexProperty.UnsetValue;
PriorityValue value;
if (result == PerspexProperty.UnsetValue)
{
result = GetDefaultValue(property);
}
if (!IsRegistered(property))
{
ThrowNotRegistered(property);
}
return result;
if (_values.TryGetValue(property, out value))
{
result = value.Value;
}
if (result == PerspexProperty.UnsetValue)
{
result = GetDefaultValue(property);
}
return result;
}
}
/// <summary>
@ -456,9 +425,16 @@ namespace Perspex
/// <returns>The value.</returns>
public T GetValue<T>(PerspexProperty<T> property)
{
Contract.Requires<NullReferenceException>(property != null);
Contract.Requires<ArgumentNullException>(property != null);
return (T)GetValue((PerspexProperty)property);
if (property.IsDirect)
{
return ((PerspexProperty<T>)GetRegistered(property)).Getter(this);
}
else
{
return (T)GetValue((PerspexProperty)property);
}
}
/// <summary>
@ -479,7 +455,7 @@ namespace Perspex
/// <returns>True if the property is set, otherwise false.</returns>
public bool IsSet(PerspexProperty property)
{
Contract.Requires<NullReferenceException>(property != null);
Contract.Requires<ArgumentNullException>(property != null);
return _values.ContainsKey(property);
}
@ -491,24 +467,7 @@ namespace Perspex
/// <returns>True if the property is registered, otherwise false.</returns>
public bool IsRegistered(PerspexProperty property)
{
Type type = GetType();
while (type != null)
{
List<PerspexProperty> list;
if (s_registered.TryGetValue(type, out list))
{
if (list.Contains(property))
{
return true;
}
}
type = type.GetTypeInfo().BaseType;
}
return false;
return FindRegistered(property) != null;
}
/// <summary>
@ -522,37 +481,50 @@ namespace Perspex
object value,
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires<NullReferenceException>(property != null);
PriorityValue v;
var originalValue = value;
Contract.Requires<ArgumentNullException>(property != null);
if (!IsRegistered(property))
if (property.IsDirect)
{
throw new InvalidOperationException(string.Format(
"Property '{0}' not registered on '{1}'",
property.Name,
GetType()));
}
property = GetRegistered(property);
if (!TypeUtilities.TryCast(property.PropertyType, value, out value))
{
throw new InvalidOperationException(string.Format(
"Invalid value for Property '{0}': '{1}' ({2})",
property.Name,
originalValue,
originalValue?.GetType().FullName ?? "(null)"));
}
if (property.Setter == null)
{
throw new ArgumentException($"The property {property.Name} is readonly.");
}
if (!_values.TryGetValue(property, out v))
property.Setter(this, value);
}
else
{
if (value == PerspexProperty.UnsetValue)
PriorityValue v;
var originalValue = value;
if (!IsRegistered(property))
{
ThrowNotRegistered(property);
}
if (!TypeUtilities.TryCast(property.PropertyType, value, out value))
{
return;
throw new ArgumentException(string.Format(
"Invalid value for Property '{0}': '{1}' ({2})",
property.Name,
originalValue,
originalValue?.GetType().FullName ?? "(null)"));
}
v = CreatePriorityValue(property);
_values.Add(property, v);
if (!_values.TryGetValue(property, out v))
{
if (value == PerspexProperty.UnsetValue)
{
return;
}
v = CreatePriorityValue(property);
_values.Add(property, v);
}
v.SetValue(value, (int)priority);
}
_propertyLog.Verbose(
@ -560,7 +532,6 @@ namespace Perspex
property,
value,
priority);
v.SetDirectValue(value, (int)priority);
}
/// <summary>
@ -575,9 +546,23 @@ namespace Perspex
T value,
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires<NullReferenceException>(property != null);
Contract.Requires<ArgumentNullException>(property != null);
if (property.IsDirect)
{
property = (PerspexProperty<T>)GetRegistered(property);
if (property.Setter == null)
{
throw new ArgumentException($"The property {property.Name} is readonly.");
}
SetValue((PerspexProperty)property, value, priority);
property.Setter(this, value);
}
else
{
SetValue((PerspexProperty)property, value, priority);
}
}
/// <summary>
@ -594,32 +579,48 @@ namespace Perspex
IObservable<object> source,
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires<NullReferenceException>(property != null);
PriorityValue v;
IDescription description = source as IDescription;
Contract.Requires<ArgumentNullException>(property != null);
if (!IsRegistered(property))
if (property.IsDirect)
{
throw new InvalidOperationException(string.Format(
"Property '{0}' not registered on '{1}'",
property.Name,
GetType()));
}
property = GetRegistered(property);
if (!_values.TryGetValue(property, out v))
{
v = CreatePriorityValue(property);
_values.Add(property, v);
if (property.Setter == null)
{
throw new ArgumentException($"The property {property.Name} is readonly.");
}
_propertyLog.Verbose(
"Bound {Property} to {Binding} with priority LocalValue",
property,
source);
return source.Subscribe(x => SetValue(property, x));
}
else
{
PriorityValue v;
IDescription description = source as IDescription;
_propertyLog.Verbose(
"Bound {Property} to {Binding} with priority {Priority}",
property,
source,
priority);
if (!IsRegistered(property))
{
ThrowNotRegistered(property);
}
if (!_values.TryGetValue(property, out v))
{
v = CreatePriorityValue(property);
_values.Add(property, v);
}
return v.Add(source, (int)priority);
_propertyLog.Verbose(
"Bound {Property} to {Binding} with priority {Priority}",
property,
source,
priority);
return v.Add(source, (int)priority);
}
}
/// <summary>
@ -637,9 +638,23 @@ namespace Perspex
IObservable<T> source,
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires<NullReferenceException>(property != null);
Contract.Requires<ArgumentNullException>(property != null);
return Bind((PerspexProperty)property, source.Select(x => (object)x), priority);
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);
}
}
/// <summary>
@ -713,6 +728,69 @@ namespace Perspex
{
}
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event.
/// </summary>
/// <param name="property">The property that has changed.</param>
/// <param name="oldValue">The old property value.</param>
/// <param name="newValue">The new property value.</param>
/// <param name="priority">The priority of the binding that produced the value.</param>
protected void RaisePropertyChanged(
PerspexProperty property,
object oldValue,
object newValue,
BindingPriority priority)
{
Contract.Requires<ArgumentNullException>(property != null);
PerspexPropertyChangedEventArgs e = new PerspexPropertyChangedEventArgs(
this,
property,
oldValue,
newValue,
priority);
OnPropertyChanged(e);
property.NotifyChanged(e);
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
if (_inpcChanged != null)
{
PropertyChangedEventArgs e2 = new PropertyChangedEventArgs(property.Name);
_inpcChanged(this, e2);
}
}
/// <summary>
/// Sets the backing field for a direct perspex property, raising the
/// <see cref="PropertyChanged"/> event if the value has changed.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="field">The backing field.</param>
/// <param name="value">The value.</param>
/// <returns>
/// True if the value changed, otherwise false.
/// </returns>
protected bool SetAndRaise<T>(PerspexProperty<T> property, ref T field, T value)
{
if (!object.Equals(field, value))
{
var old = field;
field = value;
RaisePropertyChanged(property, old, value, BindingPriority.LocalValue);
return true;
}
else
{
return false;
}
}
/// <summary>
/// Creates a <see cref="PriorityValue"/> for a <see cref="PerspexProperty"/>.
/// </summary>
@ -772,6 +850,59 @@ namespace Perspex
}
}
/// <summary>
/// Given a <see cref="PerspexProperty"/> returns a registered perspex property that is
/// equal.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>The registered property or null if not found.</returns>
/// <remarks>
/// Calling AddOwner on a direct PerspexProperty creates new new PerspexProperty with
/// an overridden getter and setter. This property is a different object but is equal
/// according to <see cref="object.Equals(object)"/>.
/// </remarks>
public PerspexProperty FindRegistered(PerspexProperty property)
{
Type type = GetType();
while (type != null)
{
List<PerspexProperty> list;
if (s_registered.TryGetValue(type, out list))
{
var index = list.IndexOf(property);
if (index != -1)
{
return list[index];
}
}
type = type.GetTypeInfo().BaseType;
}
return null;
}
/// <summary>
/// Given a <see cref="PerspexProperty"/> returns a registered perspex property that is
/// equal or throws if not found.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>The registered property.</returns>
public PerspexProperty GetRegistered(PerspexProperty property)
{
var result = FindRegistered(property);
if (result == null)
{
ThrowNotRegistered(property);
}
return result;
}
/// <summary>
/// Called when a property is changed on the current <see cref="InheritanceParent"/>.
/// </summary>
@ -801,40 +932,13 @@ namespace Perspex
}
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event.
/// Throws an exception indicating that the specified property is not registered on this
/// object.
/// </summary>
/// <param name="property">The property that has changed.</param>
/// <param name="oldValue">The old property value.</param>
/// <param name="newValue">The new property value.</param>
/// <param name="priority">The priority of the binding that produced the value.</param>
private void RaisePropertyChanged(
PerspexProperty property,
object oldValue,
object newValue,
BindingPriority priority)
/// <param name="p">The property</param>
private void ThrowNotRegistered(PerspexProperty p)
{
Contract.Requires<NullReferenceException>(property != null);
PerspexPropertyChangedEventArgs e = new PerspexPropertyChangedEventArgs(
this,
property,
oldValue,
newValue,
priority);
OnPropertyChanged(e);
property.NotifyChanged(e);
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
if (_inpcChanged != null)
{
PropertyChangedEventArgs e2 = new PropertyChangedEventArgs(property.Name);
_inpcChanged(this, e2);
}
throw new ArgumentException($"Property '{p.Name} not registered on '{this.GetType()}");
}
}
}

221
src/Perspex.Base/PerspexProperty.cs

@ -15,13 +15,18 @@ namespace Perspex
/// <remarks>
/// This class is analogous to DependencyProperty in WPF.
/// </remarks>
public class PerspexProperty
public class PerspexProperty : IEquatable<PerspexProperty>
{
/// <summary>
/// Represents an unset property value.
/// </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>
/// The default values for the property, by type.
/// </summary>
@ -43,6 +48,11 @@ namespace Perspex
private readonly Dictionary<Type, Func<PerspexObject, object, object>> _validation =
new Dictionary<Type, Func<PerspexObject, object, object>>();
/// <summary>
/// Gets the ID of the property.
/// </summary>
private int _id;
/// <summary>
/// Initializes a new instance of the <see cref="PerspexProperty"/> class.
/// </summary>
@ -64,9 +74,9 @@ namespace Perspex
Func<PerspexObject, object, object> validate = null,
bool isAttached = false)
{
Contract.Requires<NullReferenceException>(name != null);
Contract.Requires<NullReferenceException>(valueType != null);
Contract.Requires<NullReferenceException>(ownerType != null);
Contract.Requires<ArgumentNullException>(name != null);
Contract.Requires<ArgumentNullException>(valueType != null);
Contract.Requires<ArgumentNullException>(ownerType != null);
if (name.Contains("."))
{
@ -80,6 +90,7 @@ namespace Perspex
Inherits = inherits;
DefaultBindingMode = defaultBindingMode;
IsAttached = isAttached;
_id = s_nextId++;
if (validate != null)
{
@ -87,6 +98,69 @@ namespace Perspex
}
}
/// <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.");
}
Name = name;
PropertyType = valueType;
OwnerType = ownerType;
Getter = getter;
Setter = setter;
IsDirect = true;
_id = s_nextId++;
}
/// <summary>
/// Initializes a new instance of the <see cref="PerspexProperty"/> class.
/// </summary>
/// <param name="source">The direct property to copy.</param>
/// <param name="getter">A new getter.</param>
/// <param name="setter">A new setter.</param>
protected PerspexProperty(
PerspexProperty source,
Func<PerspexObject, object> getter,
Action<PerspexObject, object> setter)
{
Contract.Requires<ArgumentNullException>(source != null);
Contract.Requires<ArgumentNullException>(getter != null);
if (!source.IsDirect)
{
throw new InvalidOperationException(
"This method can only be called on direct PerspexProperties.");
}
Name = source.Name;
PropertyType = source.PropertyType;
OwnerType = source.OwnerType;
Getter = getter;
Setter = setter;
IsDirect = true;
_id = source._id;
}
/// <summary>
/// Gets the name of the property.
/// </summary>
@ -135,6 +209,11 @@ namespace Perspex
/// </value>
public bool IsAttached { get; }
/// <summary>
/// Gets a value indicating whether this is a direct property.
/// </summary>
public bool IsDirect { get; }
/// <summary>
/// Gets an observable that is fired when this property is initialized on a
/// new <see cref="PerspexObject"/> instance.
@ -191,6 +270,49 @@ 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>
/// <param name="a">The first property.</param>
/// <param name="b">The second property.</param>
/// <returns>True if the properties are equal, otherwise false.</returns>
public static bool operator ==(PerspexProperty a, PerspexProperty b)
{
if (object.ReferenceEquals(a, b))
{
return true;
}
else if (((object)a == null) || ((object)b == null))
{
return false;
}
else
{
return a.Equals(b);
}
}
/// <summary>
/// Tests two <see cref="PerspexProperty"/>s for unequality.
/// </summary>
/// <param name="a">The first property.</param>
/// <param name="b">The second property.</param>
/// <returns>True if the properties are equal, otherwise false.</returns>
public static bool operator !=(PerspexProperty a, PerspexProperty b)
{
return !(a == b);
}
/// <summary>
/// Registers a <see cref="PerspexProperty"/>.
/// </summary>
@ -210,7 +332,7 @@ namespace Perspex
Func<TOwner, TValue, TValue> validate = null)
where TOwner : PerspexObject
{
Contract.Requires<NullReferenceException>(name != null);
Contract.Requires<ArgumentNullException>(name != null);
PerspexProperty<TValue> result = new PerspexProperty<TValue>(
name,
@ -226,6 +348,34 @@ namespace Perspex
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));
PerspexObject.Register(typeof(TOwner), result);
return result;
}
/// <summary>
/// Registers an attached <see cref="PerspexProperty"/>.
/// </summary>
@ -245,7 +395,7 @@ namespace Perspex
BindingMode defaultBindingMode = BindingMode.OneWay,
Func<PerspexObject, TValue, TValue> validate = null)
{
Contract.Requires<NullReferenceException>(name != null);
Contract.Requires<ArgumentNullException>(name != null);
PerspexProperty<TValue> result = new PerspexProperty<TValue>(
name,
@ -281,7 +431,7 @@ namespace Perspex
BindingMode defaultBindingMode = BindingMode.OneWay,
Func<PerspexObject, TValue, TValue> validate = null)
{
Contract.Requires<NullReferenceException>(name != null);
Contract.Requires<ArgumentNullException>(name != null);
PerspexProperty<TValue> result = new PerspexProperty<TValue>(
name,
@ -297,6 +447,25 @@ namespace Perspex
return result;
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
var p = obj as PerspexProperty;
return p != null ? Equals(p) : false;
}
/// <inheritdoc/>
public bool Equals(PerspexProperty other)
{
return other != null && _id == other._id;
}
/// <inheritdoc/>
public override int GetHashCode()
{
return _id;
}
/// <summary>
/// Returns a binding accessor that can be passed to <see cref="PerspexObject"/>'s []
/// operator to initiate a binding.
@ -320,7 +489,7 @@ namespace Perspex
/// <returns>The default value.</returns>
public object GetDefaultValue(Type type)
{
Contract.Requires<NullReferenceException>(type != null);
Contract.Requires<ArgumentNullException>(type != null);
while (type != null)
{
@ -346,7 +515,7 @@ namespace Perspex
/// </returns>
public Func<PerspexObject, object, object> GetValidationFunc(Type type)
{
Contract.Requires<NullReferenceException>(type != null);
Contract.Requires<ArgumentNullException>(type != null);
while (type != null)
{
@ -390,7 +559,7 @@ namespace Perspex
/// <param name="defaultValue">The default value.</param>
public void OverrideDefaultValue(Type type, object defaultValue)
{
Contract.Requires<NullReferenceException>(type != null);
Contract.Requires<ArgumentNullException>(type != null);
if (!TypeUtilities.TryCast(PropertyType, defaultValue, out defaultValue))
{
@ -416,7 +585,7 @@ namespace Perspex
/// <param name="validation">The validation function.</param>
public void OverrideValidation(Type type, Func<PerspexObject, object, object> validation)
{
Contract.Requires<NullReferenceException>(type != null);
Contract.Requires<ArgumentNullException>(type != null);
if (_validation.ContainsKey(type))
{
@ -453,9 +622,37 @@ namespace Perspex
_changed.OnNext(e);
}
/// <summary>
/// Casts a getter 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> Cast<TOwner, TValue>(Func<TOwner, TValue> f)
where TOwner : PerspexObject
{
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="Perspex"/>.
/// <see cref="PerspexObject"/>.
/// </summary>
/// <typeparam name="TOwner">The owner type.</typeparam>
/// <typeparam name="TValue">The property value type.</typeparam>

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

@ -36,24 +36,87 @@ namespace Perspex
defaultValue,
inherits,
defaultBindingMode,
Convert(validate),
Cast(validate),
isAttached)
{
Contract.Requires<NullReferenceException>(name != null);
Contract.Requires<NullReferenceException>(ownerType != null);
}
/// <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 direct property to copy.</param>
/// <param name="getter">A new getter.</param>
/// <param name="setter">A new setter.</param>
private PerspexProperty(
PerspexProperty source,
Func<PerspexObject, TValue> getter,
Action<PerspexObject, TValue> setter)
: base(source, 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>()
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.");
}
PerspexObject.Register(typeof(TOwner), this);
return this;
}
/// <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
{
var result = new PerspexProperty<TValue>(this, CastReturn(getter), CastParam1(setter));
PerspexObject.Register(typeof(TOwner), result);
return result;
}
/// <summary>
/// Gets the default value for the property on the specified type.
/// </summary>
@ -78,11 +141,59 @@ namespace Perspex
}
/// <summary>
/// Converts from a typed validation function to an untyped.
/// 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> Convert(Func<PerspexObject, TValue, TValue> f)
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;
}

4
src/Perspex.Base/PriorityValue.cs

@ -105,11 +105,11 @@ namespace Perspex
}
/// <summary>
/// Sets the direct value for a specified priority.
/// Sets the value for a specified priority.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="priority">The priority</param>
public void SetDirectValue(object value, int priority)
public void SetValue(object value, int priority)
{
GetLevel(priority).DirectValue = value;
}

2
src/Perspex.Base/Utilities/TypeUtilities.cs

@ -35,7 +35,7 @@ namespace Perspex.Utilities
/// <returns>True if the cast was sucessful, otherwise false.</returns>
public static bool TryCast(Type to, object value, out object result)
{
Contract.Requires<NullReferenceException>(to != null);
Contract.Requires<ArgumentNullException>(to != null);
if (value == null)
{

14
src/Perspex.Controls/Control.cs

@ -2,14 +2,12 @@
// 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.Collections;
using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using Perspex.Input;
using Perspex.Interactivity;
using Perspex.LogicalTree;
using Perspex.Rendering;
using Perspex.Styling;
@ -46,7 +44,7 @@ namespace Perspex.Controls
/// Defines the <see cref="Parent"/> property.
/// </summary>
public static readonly PerspexProperty<IControl> ParentProperty =
PerspexProperty.Register<Control, IControl>(nameof(Parent));
PerspexProperty.RegisterDirect<Control, IControl>(nameof(Parent), o => o.Parent);
/// <summary>
/// Defines the <see cref="Tag"/> property.
@ -66,16 +64,12 @@ namespace Perspex.Controls
public static readonly RoutedEvent<RequestBringIntoViewEventArgs> RequestBringIntoViewEvent =
RoutedEvent.Register<Control, RequestBringIntoViewEventArgs>("RequestBringIntoView", RoutingStrategies.Bubble);
private IControl _parent;
private readonly Classes _classes = new Classes();
private DataTemplates _dataTemplates;
private IControl _focusAdorner;
private string _id;
private IPerspexList<ILogical> _logicalChildren;
private Styles _styles;
/// <summary>
@ -227,7 +221,7 @@ namespace Perspex.Controls
/// <summary>
/// Gets the control's logical parent.
/// </summary>
public IControl Parent => GetValue(ParentProperty);
public IControl Parent => _parent;
/// <summary>
/// Gets or sets a user-defined object attached to the control.
@ -297,7 +291,7 @@ namespace Perspex.Controls
throw new InvalidOperationException("The Control already has a parent.");
}
SetValue(ParentProperty, parent);
SetAndRaise(ParentProperty, ref _parent, (IControl)parent);
}
/// <summary>

10
src/Perspex.Controls/DropDown.cs

@ -26,11 +26,15 @@ namespace Perspex.Controls
ContentControl.VerticalContentAlignmentProperty.AddOwner<DropDown>();
public static readonly PerspexProperty<bool> IsDropDownOpenProperty =
PerspexProperty.Register<DropDown, bool>("IsDropDownOpen");
PerspexProperty.RegisterDirect<DropDown, bool>(
nameof(IsDropDownOpen),
o => o.IsDropDownOpen,
(o, v) => o.IsDropDownOpen = v);
public static readonly PerspexProperty<object> SelectionBoxItemProperty =
PerspexProperty.Register<DropDown, object>("SelectionBoxItem");
private bool _isDropDownOpen;
private Popup _popup;
static DropDown()
@ -64,8 +68,8 @@ namespace Perspex.Controls
public bool IsDropDownOpen
{
get { return GetValue(IsDropDownOpenProperty); }
set { SetValue(IsDropDownOpenProperty, value); }
get { return _isDropDownOpen; }
set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); }
}
public object SelectionBoxItem

51
src/Perspex.Controls/ItemsControl.cs

@ -33,7 +33,7 @@ namespace Perspex.Controls
/// Defines the <see cref="Items"/> property.
/// </summary>
public static readonly PerspexProperty<IEnumerable> ItemsProperty =
PerspexProperty.Register<ItemsControl, IEnumerable>(nameof(Items));
PerspexProperty.RegisterDirect<ItemsControl, IEnumerable>(nameof(Items), o => o.Items, (o, v) => o.Items = v);
/// <summary>
/// Defines the <see cref="ItemsPanel"/> property.
@ -47,6 +47,7 @@ namespace Perspex.Controls
public static readonly PerspexProperty<IMemberSelector> MemberSelectorProperty =
PerspexProperty.Register<ItemsControl, IMemberSelector>(nameof(MemberSelector));
private IEnumerable _items = new PerspexList<object>();
private IItemContainerGenerator _itemContainerGenerator;
/// <summary>
@ -63,7 +64,7 @@ namespace Perspex.Controls
public ItemsControl()
{
Classes.Add(":empty");
Items = new PerspexList<object>();
SubscribeToItems(_items);
}
/// <summary>
@ -87,8 +88,8 @@ namespace Perspex.Controls
/// </summary>
public IEnumerable Items
{
get { return GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
get { return _items; }
set { SetAndRaise(ItemsProperty, ref _items, value); }
}
/// <summary>
@ -161,26 +162,11 @@ namespace Perspex.Controls
if (incc != null)
{
incc.CollectionChanged += ItemsCollectionChanged;
incc.CollectionChanged -= ItemsCollectionChanged;
}
var newValue = e.NewValue as IEnumerable;
if (newValue == null || newValue.Count() == 0)
{
Classes.Add(":empty");
}
else
{
Classes.Remove(":empty");
}
incc = newValue as INotifyCollectionChanged;
if (incc != null)
{
incc.CollectionChanged += ItemsCollectionChanged;
}
SubscribeToItems(newValue);
}
/// <summary>
@ -202,5 +188,28 @@ namespace Perspex.Controls
Classes.Remove(":empty");
}
}
/// <summary>
/// Subscribes to an <see cref="Items"/> collection.
/// </summary>
/// <param name="items"></param>
private void SubscribeToItems(IEnumerable items)
{
if (items == null || items.Count() == 0)
{
Classes.Add(":empty");
}
else
{
Classes.Remove(":empty");
}
var incc = items as INotifyCollectionChanged;
if (incc != null)
{
incc.CollectionChanged += ItemsCollectionChanged;
}
}
}
}

10
src/Perspex.Controls/Menu.cs

@ -28,7 +28,11 @@ namespace Perspex.Controls
/// Defines the <see cref="IsOpen"/> property.
/// </summary>
public static readonly PerspexProperty<bool> IsOpenProperty =
PerspexProperty.Register<Menu, bool>(nameof(IsOpen));
PerspexProperty.RegisterDirect<Menu, bool>(
nameof(IsOpen),
o => o.IsOpen);
private bool _isOpen;
/// <summary>
/// Tracks event handlers added to the root of the visual tree.
@ -50,8 +54,8 @@ namespace Perspex.Controls
/// </summary>
public bool IsOpen
{
get { return GetValue(IsOpenProperty); }
private set { SetValue(IsOpenProperty, value); }
get { return _isOpen; }
private set { SetAndRaise(IsOpenProperty, ref _isOpen, value); }
}
/// <summary>

17
src/Perspex.Controls/Presenters/ContentPresenter.cs

@ -15,7 +15,9 @@ namespace Perspex.Controls.Presenters
/// Defines the <see cref="Child"/> property.
/// </summary>
public static readonly PerspexProperty<IControl> ChildProperty =
PerspexProperty.Register<ContentPresenter, IControl>("Child");
PerspexProperty.RegisterDirect<ContentPresenter, IControl>(
nameof(Child),
o => o.Child);
/// <summary>
/// Defines the <see cref="Content"/> property.
@ -23,6 +25,7 @@ namespace Perspex.Controls.Presenters
public static readonly PerspexProperty<object> ContentProperty =
ContentControl.ContentProperty.AddOwner<ContentPresenter>();
private IControl _child;
private bool _createdChild;
/// <summary>
@ -38,8 +41,8 @@ namespace Perspex.Controls.Presenters
/// </summary>
public IControl Child
{
get { return GetValue(ChildProperty); }
private set { SetValue(ChildProperty, value); }
get { return _child; }
private set { SetAndRaise(ChildProperty, ref _child, value); }
}
/// <summary>
@ -101,8 +104,12 @@ namespace Perspex.Controls.Presenters
var logicalHost = this.FindReparentingHost();
var logicalChildren = logicalHost?.LogicalChildren ?? LogicalChildren;
logicalChildren.Remove(old);
ClearVisualChildren();
if (old != null)
{
logicalChildren.Remove(old);
((ISetLogicalParent)old).SetParent(null);
ClearVisualChildren();
}
Child = result;

19
src/Perspex.Controls/Presenters/DeckPresenter.cs

@ -23,7 +23,7 @@ namespace Perspex.Controls.Presenters
/// Defines the <see cref="Items"/> property.
/// </summary>
public static readonly PerspexProperty<IEnumerable> ItemsProperty =
ItemsControl.ItemsProperty.AddOwner<DeckPresenter>();
ItemsControl.ItemsProperty.AddOwner<DeckPresenter>(o => o.Items, (o, v) => o.Items = v);
/// <summary>
/// Defines the <see cref="ItemsPanel"/> property.
@ -41,7 +41,9 @@ namespace Perspex.Controls.Presenters
/// Defines the <see cref="SelectedIndex"/> property.
/// </summary>
public static readonly PerspexProperty<int> SelectedIndexProperty =
SelectingItemsControl.SelectedIndexProperty.AddOwner<DeckPresenter>();
SelectingItemsControl.SelectedIndexProperty.AddOwner<DeckPresenter>(
o => o.SelectedIndex,
(o, v) => o.SelectedIndex = v);
/// <summary>
/// Defines the <see cref="Transition"/> property.
@ -49,8 +51,9 @@ namespace Perspex.Controls.Presenters
public static readonly PerspexProperty<IPageTransition> TransitionProperty =
Deck.TransitionProperty.AddOwner<DeckPresenter>();
private IEnumerable _items;
private int _selectedIndex = -1;
private bool _createdPanel;
private IItemContainerGenerator _generator;
/// <summary>
@ -94,8 +97,8 @@ namespace Perspex.Controls.Presenters
/// </summary>
public IEnumerable Items
{
get { return GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
get { return _items; }
set { SetAndRaise(ItemsProperty, ref _items, value); }
}
/// <summary>
@ -121,8 +124,8 @@ namespace Perspex.Controls.Presenters
/// </summary>
public int SelectedIndex
{
get { return GetValue(SelectedIndexProperty); }
set { SetValue(SelectedIndexProperty, value); }
get { return _selectedIndex; }
set { SetAndRaise(SelectedIndexProperty, ref _selectedIndex, value); }
}
/// <summary>
@ -204,7 +207,7 @@ namespace Perspex.Controls.Presenters
}
}
if (Transition != null)
if (Transition != null && (from != null || to != null))
{
await Transition.Start((Visual)from, (Visual)to, fromIndex < toIndex);
}

8
src/Perspex.Controls/Presenters/ItemsPresenter.cs

@ -20,7 +20,7 @@ namespace Perspex.Controls.Presenters
/// Defines the <see cref="Items"/> property.
/// </summary>
public static readonly PerspexProperty<IEnumerable> ItemsProperty =
ItemsControl.ItemsProperty.AddOwner<ItemsPresenter>();
ItemsControl.ItemsProperty.AddOwner<ItemsPresenter>(o => o.Items, (o, v) => o.Items = v);
/// <summary>
/// Defines the <see cref="ItemsPanel"/> property.
@ -34,8 +34,8 @@ namespace Perspex.Controls.Presenters
public static readonly PerspexProperty<IMemberSelector> MemberSelectorProperty =
ItemsControl.MemberSelectorProperty.AddOwner<ItemsPresenter>();
private IEnumerable _items;
private bool _createdPanel;
private IItemContainerGenerator _generator;
/// <summary>
@ -89,8 +89,8 @@ namespace Perspex.Controls.Presenters
/// </summary>
public IEnumerable Items
{
get { return GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
get { return _items; }
set { SetAndRaise(ItemsProperty, ref _items, value); }
}
/// <summary>

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

@ -31,18 +31,19 @@ namespace Perspex.Controls.Primitives
/// Defines the <see cref="SelectedIndex"/> property.
/// </summary>
public static readonly PerspexProperty<int> SelectedIndexProperty =
PerspexProperty.Register<SelectingItemsControl, int>(
PerspexProperty.RegisterDirect<SelectingItemsControl, int>(
nameof(SelectedIndex),
defaultValue: -1,
validate: ValidateSelectedIndex);
o => o.SelectedIndex,
(o, v) => o.SelectedIndex = v);
/// <summary>
/// Defines the <see cref="SelectedItem"/> property.
/// </summary>
public static readonly PerspexProperty<object> SelectedItemProperty =
PerspexProperty.Register<SelectingItemsControl, object>(
PerspexProperty.RegisterDirect<SelectingItemsControl, object>(
nameof(SelectedItem),
validate: ValidateSelectedItem);
o => o.SelectedItem,
(o, v) => o.SelectedItem = v);
/// <summary>
/// Event that should be raised by items that implement <see cref="ISelectable"/> to
@ -52,6 +53,9 @@ namespace Perspex.Controls.Primitives
public static readonly RoutedEvent<RoutedEventArgs> IsSelectedChangedEvent =
RoutedEvent.Register<SelectingItemsControl, RoutedEventArgs>("IsSelectedChanged", RoutingStrategies.Bubble);
private int _selectedIndex = -1;
private object _selectedItem;
/// <summary>
/// Initializes static members of the <see cref="SelectingItemsControl"/> class.
/// </summary>
@ -85,8 +89,16 @@ namespace Perspex.Controls.Primitives
/// </summary>
public int SelectedIndex
{
get { return GetValue(SelectedIndexProperty); }
set { SetValue(SelectedIndexProperty, value); }
get
{
return _selectedIndex;
}
set
{
value = (value >= 0 && value < Items?.Cast<object>().Count()) ? value : -1;
SetAndRaise(SelectedIndexProperty, ref _selectedIndex, value);
}
}
/// <summary>
@ -94,8 +106,16 @@ namespace Perspex.Controls.Primitives
/// </summary>
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
get
{
return _selectedItem;
}
set
{
value = Items?.Cast<object>().Contains(value) == true ? value : null;
SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
}
}
/// <inheritdoc/>
@ -234,30 +254,6 @@ namespace Perspex.Controls.Primitives
}
}
/// <summary>
/// Coerces the <see cref="SelectedIndex"/> property.
/// </summary>
/// <param name="sender">The object with the property.</param>
/// <param name="index">The proposed value of the property.</param>
/// <returns>The final value of the property.</returns>
private static int ValidateSelectedIndex(SelectingItemsControl sender, int index)
{
var items = sender.Items;
return (index >= 0 && index < items?.Cast<object>().Count()) ? index : -1;
}
/// <summary>
/// Coerces the <see cref="SelectedItem"/> property.
/// </summary>
/// <param name="sender">The object with the property.</param>
/// <param name="item">The proposed value of the property.</param>
/// <returns>The final value of the property.</returns>
private static object ValidateSelectedItem(SelectingItemsControl sender, object item)
{
var items = sender.Items;
return items?.Cast<object>().Contains(item) == true ? item : null;
}
/// <summary>
/// Called when new containers are initialized by the <see cref="ItemContainerGenerator"/>.
/// </summary>

36
src/Perspex.Controls/TopLevel.cs

@ -29,13 +29,13 @@ namespace Perspex.Controls
/// Defines the <see cref="ClientSize"/> property.
/// </summary>
public static readonly PerspexProperty<Size> ClientSizeProperty =
PerspexProperty.Register<TopLevel, Size>("ClientSize");
PerspexProperty.RegisterDirect<TopLevel, Size>(nameof(ClientSize), o => o.ClientSize);
/// <summary>
/// Defines the <see cref="IsActive"/> property.
/// </summary>
public static readonly PerspexProperty<bool> IsActiveProperty =
PerspexProperty.Register<TopLevel, bool>("IsActive");
PerspexProperty.RegisterDirect<TopLevel, bool>(nameof(IsActive), o => o.IsActive);
/// <summary>
/// Defines the <see cref="IInputRoot.PointerOverElement"/> property.
@ -43,30 +43,13 @@ namespace Perspex.Controls
public static readonly PerspexProperty<IInputElement> PointerOverElementProperty =
PerspexProperty.Register<TopLevel, IInputElement>(nameof(IInputRoot.PointerOverElement));
/// <summary>
/// The render manager for the window.s
/// </summary>
private readonly IRenderManager _renderManager;
/// <summary>
/// The window renderer.
/// </summary>
private readonly IRenderer _renderer;
/// <summary>
/// The input manager for the window.
/// </summary>
private readonly IInputManager _inputManager;
/// <summary>
/// The access key handler for the window.
/// </summary>
private readonly IAccessKeyHandler _accessKeyHandler;
/// <summary>
/// The access keyboard navigation handler for the window.
/// </summary>
private readonly IKeyboardNavigationHandler _keyboardNavigationHandler;
private Size _clientSize;
private bool _isActive;
/// <summary>
/// Initializes static members of the <see cref="TopLevel"/> class.
@ -177,8 +160,8 @@ namespace Perspex.Controls
/// </summary>
public Size ClientSize
{
get { return GetValue(ClientSizeProperty); }
private set { SetValue(ClientSizeProperty, value); }
get { return _clientSize; }
private set { SetAndRaise(ClientSizeProperty, ref _clientSize, value); }
}
/// <summary>
@ -186,8 +169,8 @@ namespace Perspex.Controls
/// </summary>
public bool IsActive
{
get { return GetValue(IsActiveProperty); }
private set { SetValue(IsActiveProperty, value); }
get { return _isActive; }
private set { SetAndRaise(IsActiveProperty, ref _isActive, value); }
}
/// <summary>
@ -195,7 +178,8 @@ namespace Perspex.Controls
/// </summary>
public ILayoutManager LayoutManager
{
get; }
get;
}
/// <summary>
/// Gets the platform-specific window implementation.

15
src/Perspex.Input/InputElement.cs

@ -43,7 +43,7 @@ namespace Perspex.Input
/// Defines the <see cref="IsFocused"/> property.
/// </summary>
public static readonly PerspexProperty<bool> IsFocusedProperty =
PerspexProperty.Register<InputElement, bool>("IsFocused");
PerspexProperty.RegisterDirect<InputElement, bool>("IsFocused", o => o.IsFocused);
/// <summary>
/// Defines the <see cref="IsHitTestVisible"/> property.
@ -55,7 +55,7 @@ namespace Perspex.Input
/// Defines the <see cref="IsPointerOver"/> property.
/// </summary>
public static readonly PerspexProperty<bool> IsPointerOverProperty =
PerspexProperty.Register<InputElement, bool>("IsPointerOver");
PerspexProperty.RegisterDirect<InputElement, bool>("IsPointerOver", o => o.IsPointerOver);
/// <summary>
/// Defines the <see cref="GotFocus"/> event.
@ -137,6 +137,9 @@ namespace Perspex.Input
"PointerWheelChanged",
RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
private bool _isFocused;
private bool _isPointerOver;
/// <summary>
/// Initializes static members of the <see cref="InputElement"/> class.
/// </summary>
@ -288,8 +291,8 @@ namespace Perspex.Input
/// </summary>
public bool IsFocused
{
get { return GetValue(IsFocusedProperty); }
private set { SetValue(IsFocusedProperty, value); }
get { return _isFocused; }
private set { SetAndRaise(IsFocusedProperty, ref _isFocused, value); }
}
/// <summary>
@ -306,8 +309,8 @@ namespace Perspex.Input
/// </summary>
public bool IsPointerOver
{
get { return GetValue(IsPointerOverProperty); }
internal set { SetValue(IsPointerOverProperty, value); }
get { return _isPointerOver; }
internal set { SetAndRaise(IsPointerOverProperty, ref _isPointerOver, value); }
}
/// <summary>

2
src/Perspex.Input/InputExtensions.cs

@ -11,7 +11,7 @@ namespace Perspex.Input
{
public static IEnumerable<IInputElement> GetInputElementsAt(this IInputElement element, Point p)
{
Contract.Requires<NullReferenceException>(element != null);
Contract.Requires<ArgumentNullException>(element != null);
if (element.Bounds.Contains(p) &&
element.IsVisible &&

18
src/Perspex.Interactivity/Interactive.cs

@ -4,11 +4,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Perspex.Layout;
using Perspex.VisualTree;
namespace Perspex.Interactivity
{
@ -39,8 +37,8 @@ namespace Perspex.Interactivity
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
bool handledEventsToo = false)
{
Contract.Requires<NullReferenceException>(routedEvent != null);
Contract.Requires<NullReferenceException>(handler != null);
Contract.Requires<ArgumentNullException>(routedEvent != null);
Contract.Requires<ArgumentNullException>(handler != null);
List<EventSubscription> subscriptions;
@ -87,8 +85,8 @@ namespace Perspex.Interactivity
/// <param name="handler">The handler.</param>
public void RemoveHandler(RoutedEvent routedEvent, Delegate handler)
{
Contract.Requires<NullReferenceException>(routedEvent != null);
Contract.Requires<NullReferenceException>(handler != null);
Contract.Requires<ArgumentNullException>(routedEvent != null);
Contract.Requires<ArgumentNullException>(handler != null);
List<EventSubscription> subscriptions;
@ -116,7 +114,7 @@ namespace Perspex.Interactivity
/// <param name="e">The event args.</param>
public void RaiseEvent(RoutedEventArgs e)
{
Contract.Requires<NullReferenceException>(e != null);
Contract.Requires<ArgumentNullException>(e != null);
e.Source = e.Source ?? this;
@ -143,7 +141,7 @@ namespace Perspex.Interactivity
/// <param name="e">The event args.</param>
private void BubbleEvent(RoutedEventArgs e)
{
Contract.Requires<NullReferenceException>(e != null);
Contract.Requires<ArgumentNullException>(e != null);
e.Route = RoutingStrategies.Bubble;
@ -159,7 +157,7 @@ namespace Perspex.Interactivity
/// <param name="e">The event args.</param>
private void TunnelEvent(RoutedEventArgs e)
{
Contract.Requires<NullReferenceException>(e != null);
Contract.Requires<ArgumentNullException>(e != null);
e.Route = RoutingStrategies.Tunnel;
@ -175,7 +173,7 @@ namespace Perspex.Interactivity
/// <param name="e">The event args.</param>
private void RaiseEventImpl(RoutedEventArgs e)
{
Contract.Requires<NullReferenceException>(e != null);
Contract.Requires<ArgumentNullException>(e != null);
e.RoutedEvent.InvokeClassHandlers(this, e);

14
src/Perspex.Interactivity/RoutedEvent.cs

@ -26,9 +26,9 @@ namespace Perspex.Interactivity
Type eventArgsType,
Type ownerType)
{
Contract.Requires<NullReferenceException>(name != null);
Contract.Requires<NullReferenceException>(eventArgsType != null);
Contract.Requires<NullReferenceException>(ownerType != null);
Contract.Requires<ArgumentNullException>(name != null);
Contract.Requires<ArgumentNullException>(eventArgsType != null);
Contract.Requires<ArgumentNullException>(ownerType != null);
Contract.Requires<InvalidCastException>(typeof(RoutedEventArgs).GetTypeInfo().IsAssignableFrom(eventArgsType.GetTypeInfo()));
EventArgsType = eventArgsType;
@ -66,7 +66,7 @@ namespace Perspex.Interactivity
RoutingStrategies routingStrategy)
where TEventArgs : RoutedEventArgs
{
Contract.Requires<NullReferenceException>(name != null);
Contract.Requires<ArgumentNullException>(name != null);
return new RoutedEvent<TEventArgs>(name, routingStrategy, typeof(TOwner));
}
@ -77,7 +77,7 @@ namespace Perspex.Interactivity
Type ownerType)
where TEventArgs : RoutedEventArgs
{
Contract.Requires<NullReferenceException>(name != null);
Contract.Requires<ArgumentNullException>(name != null);
return new RoutedEvent<TEventArgs>(name, routingStrategy, ownerType);
}
@ -117,8 +117,8 @@ namespace Perspex.Interactivity
public RoutedEvent(string name, RoutingStrategies routingStrategies, Type ownerType)
: base(name, routingStrategies, typeof(TEventArgs), ownerType)
{
Contract.Requires<NullReferenceException>(name != null);
Contract.Requires<NullReferenceException>(ownerType != null);
Contract.Requires<ArgumentNullException>(name != null);
Contract.Requires<ArgumentNullException>(ownerType != null);
}
public void AddClassHandler<TTarget>(

21
src/Perspex.SceneGraph/Visual.cs

@ -32,7 +32,7 @@ namespace Perspex
/// Defines the <see cref="Bounds"/> property.
/// </summary>
public static readonly PerspexProperty<Rect> BoundsProperty =
PerspexProperty.Register<Visual, Rect>(nameof(Bounds));
PerspexProperty.RegisterDirect<Visual, Rect>(nameof(Bounds), o => o.Bounds);
/// <summary>
/// Defines the <see cref="ClipToBounds"/> property.
@ -64,6 +64,12 @@ namespace Perspex
public static readonly PerspexProperty<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 =
PerspexProperty.RegisterDirect<Visual, IVisual>("VisualParent", o => o._visualParent);
/// <summary>
/// Defines the <see cref="ZIndex"/> property.
/// </summary>
@ -75,10 +81,15 @@ namespace Perspex
/// </summary>
private readonly PerspexList<IVisual> _visualChildren;
/// <summary>
/// The visual's bounds relative to its parent.
/// </summary>
private Rect _bounds;
/// <summary>
/// Holds the parent of the visual.
/// </summary>
private Visual _visualParent;
private IVisual _visualParent;
/// <summary>
/// Whether the element is attached to the visual tree.
@ -121,8 +132,8 @@ namespace Perspex
/// </summary>
public Rect Bounds
{
get { return GetValue(BoundsProperty); }
protected set { SetValue(BoundsProperty, value); }
get { return _bounds; }
protected set { SetAndRaise(BoundsProperty, ref _bounds, value); }
}
/// <summary>
@ -439,6 +450,8 @@ namespace Perspex
{
NotifyAttachedToVisualTree(newRoot);
}
RaisePropertyChanged(VisualParentProperty, old, value, BindingPriority.LocalValue);
}
}

8
src/Perspex.SceneGraph/VisualTree/VisualExtensions.cs

@ -19,7 +19,7 @@ namespace Perspex.VisualTree
/// <returns>The visual's ancestors.</returns>
public static IEnumerable<IVisual> GetVisualAncestors(this IVisual visual)
{
Contract.Requires<NullReferenceException>(visual != null);
Contract.Requires<ArgumentNullException>(visual != null);
visual = visual.VisualParent;
@ -53,7 +53,7 @@ namespace Perspex.VisualTree
/// <returns>The visuals at the requested point.</returns>
public static IVisual GetVisualAt(this IVisual visual, Point p)
{
Contract.Requires<NullReferenceException>(visual != null);
Contract.Requires<ArgumentNullException>(visual != null);
return visual.GetVisualsAt(p).FirstOrDefault();
}
@ -66,7 +66,7 @@ namespace Perspex.VisualTree
/// <returns>The visuals at the requested point.</returns>
public static IEnumerable<IVisual> GetVisualsAt(this IVisual visual, Point p)
{
Contract.Requires<NullReferenceException>(visual != null);
Contract.Requires<ArgumentNullException>(visual != null);
if (visual.Bounds.Contains(p))
{
@ -162,7 +162,7 @@ namespace Perspex.VisualTree
/// </returns>
public static IVisual GetVisualRoot(this IVisual visual)
{
Contract.Requires<NullReferenceException>(visual != null);
Contract.Requires<ArgumentNullException>(visual != null);
var parent = visual.VisualParent;

2
src/Perspex.Styling/LogicalTree/LogicalExtensions.cs

@ -11,7 +11,7 @@ namespace Perspex.LogicalTree
{
public static IEnumerable<ILogical> GetLogicalAncestors(this ILogical logical)
{
Contract.Requires<NullReferenceException>(logical != null);
Contract.Requires<ArgumentNullException>(logical != null);
logical = logical.LogicalParent;

2
src/Perspex.Styling/Styling/StyleBinding.cs

@ -86,7 +86,7 @@ namespace Perspex.Styling
/// <returns>IDisposable object used to unsubscribe from the observable sequence.</returns>
protected override IDisposable SubscribeCore(IObserver<object> observer)
{
Contract.Requires<NullReferenceException>(observer != null);
Contract.Requires<ArgumentNullException>(observer != null);
if (Source == null)
{

1
tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj

@ -73,6 +73,7 @@
<Compile Include="Collections\PerspexDictionaryTests.cs" />
<Compile Include="Collections\PerspexListTests.cs" />
<Compile Include="Collections\PropertyChangedTracker.cs" />
<Compile Include="PerspexObjectTests_Direct.cs" />
<Compile Include="PerspexObjectTests_GetObservable.cs" />
<Compile Include="PerspexObjectTests_Validation.cs" />
<Compile Include="PerspexObjectTests_Binding.cs" />

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

@ -39,7 +39,7 @@ namespace Perspex.Base.UnitTests
{
Class1 target = new Class1();
Assert.Throws<InvalidOperationException>(() =>
Assert.Throws<ArgumentException>(() =>
{
target.Bind(Class2.BarProperty, Observable.Return("foo"));
});
@ -212,7 +212,7 @@ namespace Perspex.Base.UnitTests
{
Class1 target = new Class1();
Assert.Throws<InvalidOperationException>(() =>
Assert.Throws<ArgumentException>(() =>
{
target[Class1.FooProperty] = Observable.Return("newvalue");
});

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

@ -0,0 +1,332 @@
// 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.Reactive.Subjects;
using Xunit;
namespace Perspex.Base.UnitTests
{
public class PerspexObjectTests_Direct
{
[Fact]
public void GetValue_Gets_Value()
{
var target = new Class1();
Assert.Equal("initial", target.GetValue(Class1.FooProperty));
}
[Fact]
public void GetValue_Gets_Value_NonGeneric()
{
var target = new Class1();
Assert.Equal("initial", target.GetValue((PerspexProperty)Class1.FooProperty));
}
[Fact]
public void GetValue_On_Unregistered_Property_Throws_Exception()
{
var target = new Class2();
Assert.Throws<ArgumentException>(() => target.GetValue(Class1.BarProperty));
}
[Fact]
public void SetValue_Sets_Value()
{
var target = new Class1();
target.SetValue(Class1.FooProperty, "newvalue");
Assert.Equal("newvalue", target.Foo);
}
[Fact]
public void SetValue_Sets_Value_NonGeneric()
{
var target = new Class1();
target.SetValue((PerspexProperty)Class1.FooProperty, "newvalue");
Assert.Equal("newvalue", target.Foo);
}
[Fact]
public void SetValue_Raises_PropertyChanged()
{
var target = new Class1();
bool raised = false;
target.PropertyChanged += (s, e) =>
raised = e.Property == Class1.FooProperty &&
(string)e.OldValue == "initial" &&
(string)e.NewValue == "newvalue" &&
e.Priority == BindingPriority.LocalValue;
target.SetValue(Class1.FooProperty, "newvalue");
Assert.True(raised);
}
[Fact]
public void SetValue_Raises_Changed()
{
var target = new Class1();
bool raised = false;
Class1.FooProperty.Changed.Subscribe(e =>
raised = e.Property == Class1.FooProperty &&
(string)e.OldValue == "initial" &&
(string)e.NewValue == "newvalue" &&
e.Priority == BindingPriority.LocalValue);
target.SetValue(Class1.FooProperty, "newvalue");
Assert.True(raised);
}
[Fact]
public void SetValue_On_Unregistered_Property_Throws_Exception()
{
var target = new Class2();
Assert.Throws<ArgumentException>(() => target.SetValue(Class1.BarProperty, "value"));
}
[Fact]
public void GetObservable_Returns_Values()
{
var target = new Class1();
List<string> values = new List<string>();
target.GetObservable(Class1.FooProperty).Subscribe(x => values.Add(x));
target.Foo = "newvalue";
Assert.Equal(new[] { "initial", "newvalue" }, values);
}
[Fact]
public void Bind_Binds_Property_Value()
{
var target = new Class1();
var source = new Subject<string>();
var sub = target.Bind(Class1.FooProperty, source);
Assert.Equal("initial", target.Foo);
source.OnNext("first");
Assert.Equal("first", target.Foo);
source.OnNext("second");
Assert.Equal("second", target.Foo);
sub.Dispose();
source.OnNext("third");
Assert.Equal("second", target.Foo);
}
[Fact]
public void Bind_Binds_Property_Value_NonGeneric()
{
var target = new Class1();
var source = new Subject<string>();
var sub = target.Bind((PerspexProperty)Class1.FooProperty, source);
Assert.Equal("initial", target.Foo);
source.OnNext("first");
Assert.Equal("first", target.Foo);
source.OnNext("second");
Assert.Equal("second", target.Foo);
sub.Dispose();
source.OnNext("third");
Assert.Equal("second", target.Foo);
}
[Fact]
public void ReadOnly_Property_Cannot_Be_Set()
{
var target = new Class1();
Assert.Throws<ArgumentException>(() =>
target.SetValue(Class1.BarProperty, "newvalue"));
}
[Fact]
public void ReadOnly_Property_Cannot_Be_Set_NonGeneric()
{
var target = new Class1();
Assert.Throws<ArgumentException>(() =>
target.SetValue((PerspexProperty)Class1.BarProperty, "newvalue"));
}
[Fact]
public void ReadOnly_Property_Cannot_Be_Bound()
{
var target = new Class1();
var source = new Subject<string>();
Assert.Throws<ArgumentException>(() =>
target.Bind(Class1.BarProperty, source));
}
[Fact]
public void ReadOnly_Property_Cannot_Be_Bound_NonGeneric()
{
var target = new Class1();
var source = new Subject<string>();
Assert.Throws<ArgumentException>(() =>
target.Bind(Class1.BarProperty, source));
}
[Fact]
public void GetValue_Gets_Value_On_AddOwnered_Property()
{
var target = new Class2();
Assert.Equal("initial2", target.GetValue(Class2.FooProperty));
}
[Fact]
public void GetValue_Gets_Value_On_AddOwnered_Property_Using_Original()
{
var target = new Class2();
Assert.Equal("initial2", target.GetValue(Class1.FooProperty));
}
[Fact]
public void GetValue_Gets_Value_On_AddOwnered_Property_Using_Original_NonGeneric()
{
var target = new Class2();
Assert.Equal("initial2", target.GetValue((PerspexProperty)Class1.FooProperty));
}
[Fact]
public void SetValue_Sets_Value_On_AddOwnered_Property_Using_Original()
{
var target = new Class2();
target.SetValue(Class1.FooProperty, "newvalue");
Assert.Equal("newvalue", target.Foo);
}
[Fact]
public void SetValue_Sets_Value_On_AddOwnered_Property_Using_Original_NonGeneric()
{
var target = new Class2();
target.SetValue((PerspexProperty)Class1.FooProperty, "newvalue");
Assert.Equal("newvalue", target.Foo);
}
[Fact]
public void Bind_Binds_AddOwnered_Property_Value()
{
var target = new Class2();
var source = new Subject<string>();
var sub = target.Bind(Class1.FooProperty, source);
Assert.Equal("initial2", target.Foo);
source.OnNext("first");
Assert.Equal("first", target.Foo);
source.OnNext("second");
Assert.Equal("second", target.Foo);
sub.Dispose();
source.OnNext("third");
Assert.Equal("second", target.Foo);
}
[Fact]
public void Bind_Binds_AddOwnered_Property_Value_NonGeneric()
{
var target = new Class2();
var source = new Subject<string>();
var sub = target.Bind((PerspexProperty)Class1.FooProperty, source);
Assert.Equal("initial2", target.Foo);
source.OnNext("first");
Assert.Equal("first", target.Foo);
source.OnNext("second");
Assert.Equal("second", target.Foo);
sub.Dispose();
source.OnNext("third");
Assert.Equal("second", target.Foo);
}
[Fact]
public void Property_Notifies_Initialized()
{
Class1 target;
bool raised = false;
Class1.FooProperty.Initialized.Subscribe(e =>
raised = e.Property == Class1.FooProperty &&
e.OldValue == PerspexProperty.UnsetValue &&
(string)e.NewValue == "initial" &&
e.Priority == BindingPriority.Unset);
target = new Class1();
Assert.True(raised);
}
private class Class1 : PerspexObject
{
public static readonly PerspexProperty<string> FooProperty =
PerspexProperty.RegisterDirect<Class1, string>("Foo", o => o.Foo, (o, v) => o.Foo = v);
public static readonly PerspexProperty<string> BarProperty =
PerspexProperty.RegisterDirect<Class1, string>("Bar", o => o.Bar);
private string _foo = "initial";
private string _bar = "bar";
public string Foo
{
get { return _foo; }
set { SetAndRaise(FooProperty, ref _foo, value); }
}
public string Bar
{
get { return _bar; }
}
}
private class Class2 : PerspexObject
{
public static readonly PerspexProperty<string> FooProperty =
Class1.FooProperty.AddOwner<Class2>(o => o.Foo, (o, v) => o.Foo = v);
private string _foo = "initial2";
static Class2()
{
}
public string Foo
{
get { return _foo; }
set { SetAndRaise(FooProperty, ref _foo, value); }
}
}
}
}

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

@ -1,6 +1,7 @@
// 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 Xunit;
namespace Perspex.Base.UnitTests
@ -44,6 +45,14 @@ namespace Perspex.Base.UnitTests
Assert.Equal("changed", child.GetValue(Class1.BazProperty));
}
[Fact]
public void GetValue_Throws_Exception_For_Unregistered_Property()
{
var target = new Class3();
Assert.Throws<ArgumentException>(() => target.GetValue(Class1.FooProperty));
}
private class Class1 : PerspexObject
{
public static readonly PerspexProperty<string> FooProperty =
@ -66,5 +75,9 @@ namespace Perspex.Base.UnitTests
set { InheritanceParent = value; }
}
}
private class Class3 : PerspexObject
{
}
}
}

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

@ -87,7 +87,7 @@ namespace Perspex.Base.UnitTests
{
Class1 target = new Class1();
Assert.Throws<InvalidOperationException>(() =>
Assert.Throws<ArgumentException>(() =>
{
target.SetValue(Class2.BarProperty, "invalid");
});
@ -98,7 +98,7 @@ namespace Perspex.Base.UnitTests
{
Class1 target = new Class1();
Assert.Throws<InvalidOperationException>(() =>
Assert.Throws<ArgumentException>(() =>
{
target.SetValue(Class1.FooProperty, 123);
});

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

@ -125,6 +125,69 @@ 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) => { });
Assert.True(target.IsDirect);
}
[Fact]
public void Property_Equals_Should_Handle_Null()
{
var p1 = new PerspexProperty<string>("p1", typeof(Class1));
Assert.NotEqual(p1, null);
Assert.NotEqual(null, p1);
Assert.False(p1 == null);
Assert.False(null == p1);
Assert.False(p1.Equals(null));
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_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 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 Class1 : PerspexObject
{
public static readonly PerspexProperty<string> FooProperty =
@ -135,7 +198,7 @@ namespace Perspex.Base.UnitTests
{
}
private class Class3
private class Class3 : PerspexObject
{
}
}

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

@ -47,7 +47,7 @@ namespace Perspex.Base.UnitTests
var target = new PriorityValue("Test", typeof(string));
target.Add(Single("foo"), 0);
target.SetDirectValue("bar", 0);
target.SetValue("bar", 0);
Assert.Equal("bar", target.Value);
}
@ -60,7 +60,7 @@ namespace Perspex.Base.UnitTests
target.Add(source, 0);
Assert.Equal("initial", target.Value);
target.SetDirectValue("first", 0);
target.SetValue("first", 0);
Assert.Equal("first", target.Value);
source.OnNext("second");
Assert.Equal("second", target.Value);
@ -76,7 +76,7 @@ namespace Perspex.Base.UnitTests
target.Add(nonActive, 0);
target.Add(source, 0);
Assert.Equal("initial", target.Value);
target.SetDirectValue("first", 0);
target.SetValue("first", 0);
Assert.Equal("first", target.Value);
nonActive.OnNext("second");
Assert.Equal("second", target.Value);
@ -92,7 +92,7 @@ namespace Perspex.Base.UnitTests
target.Add(nonActive, 1);
target.Add(source, 1);
Assert.Equal("initial", target.Value);
target.SetDirectValue("first", 1);
target.SetValue("first", 1);
Assert.Equal("first", target.Value);
nonActive.OnNext("second");
Assert.Equal("first", target.Value);
@ -106,7 +106,7 @@ namespace Perspex.Base.UnitTests
target.Add(source, 0);
Assert.Equal("initial", target.Value);
target.SetDirectValue("first", 0);
target.SetValue("first", 0);
Assert.Equal("first", target.Value);
source.OnNext("second");
Assert.Equal("second", target.Value);
@ -267,9 +267,9 @@ namespace Perspex.Base.UnitTests
{
var target = new PriorityValue("Test", typeof(int), x => Math.Min((int)x, 10));
target.SetDirectValue(5, 0);
target.SetValue(5, 0);
Assert.Equal(5, target.Value);
target.SetDirectValue(15, 0);
target.SetValue(15, 0);
Assert.Equal(10, target.Value);
}

18
tests/Perspex.Controls.UnitTests/ContentPresenterTests.cs

@ -4,6 +4,7 @@
using System.Collections.Specialized;
using System.Linq;
using Perspex.Controls.Presenters;
using Perspex.LogicalTree;
using Xunit;
namespace Perspex.Controls.UnitTests
@ -29,11 +30,28 @@ namespace Perspex.Controls.UnitTests
var child = new Control();
target.Content = child;
target.ApplyTemplate();
target.Content = null;
target.ApplyTemplate();
Assert.Equal(new ILogical[0], ((ILogical)target).LogicalChildren.ToList());
}
[Fact]
public void Clearing_Content_Clear_Childs_Parent()
{
var target = new ContentPresenter();
var child = new Control();
target.Content = child;
target.ApplyTemplate();
target.Content = null;
target.ApplyTemplate();
Assert.Null(child.Parent);
Assert.Null(child.GetLogicalParent());
}
[Fact]
public void Changing_Content_Should_Fire_LogicalChildren_CollectionChanged()
{

1
tests/Perspex.Controls.UnitTests/Primitives/TabStripTests.cs

@ -33,6 +33,7 @@ namespace Perspex.Controls.UnitTests.Primitives
target.ApplyTemplate();
Assert.Equal(0, target.SelectedIndex);
Assert.Equal(target.Items.Cast<TabItem>().First(), target.SelectedItem);
Assert.Equal(target.Items.Cast<TabItem>().First(), target.SelectedTab);
}

6
tests/Perspex.Controls.UnitTests/Utils/HotKeyManagerTests.cs

@ -43,11 +43,13 @@ namespace Perspex.Controls.UnitTests.Utils
HotKeyManager.SetHotKey(button, gesture2);
Assert.Equal(gesture2, tl.KeyBindings[0].Gesture);
button.SetValue(Control.ParentProperty, null);
tl.Content = null;
tl.Presenter.ApplyTemplate();
Assert.Empty(tl.KeyBindings);
button.SetValue(Control.ParentProperty, tl);
tl.Content = button;
tl.Presenter.ApplyTemplate();
Assert.Equal(gesture2, tl.KeyBindings[0].Gesture);

6
tests/Perspex.Markup.Xaml.UnitTests/Properties/AssemblyInfo.cs

@ -2,5 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Reflection;
using Xunit;
[assembly: AssemblyTitle("Perspex.Markup.Xaml.UnitTests")]
[assembly: AssemblyTitle("Perspex.Markup.Xaml.UnitTests")]
// Don't run tests in parallel.
[assembly: CollectionBehavior(DisableTestParallelization = true)]

16
tests/Perspex.SceneGraph.UnitTests/VisualTests.cs

@ -1,6 +1,8 @@
// 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.Linq;
using Perspex.VisualTree;
using Xunit;
@ -31,6 +33,20 @@ namespace Perspex.SceneGraph.UnitTests
Assert.Equal(target, child.InheritanceParent);
}
[Fact]
public void Added_Child_Should_Notify_VisualParent_Changed()
{
var target = new TestVisual();
var child = new TestVisual();
var parents = new List<IVisual>();
child.GetObservable(Visual.VisualParentProperty).Subscribe(x => parents.Add(x));
target.AddChild(child);
target.RemoveChild(child);
Assert.Equal(new IVisual[] { null, target, null }, parents);
}
[Fact]
public void Removed_Child_Should_Have_VisualParent_Cleared()
{

Loading…
Cancel
Save