Browse Source

Merge remote-tracking branch 'refs/remotes/origin/binding-refactor' into devtools

pull/403/head
Steven Kirk 10 years ago
parent
commit
8606a77d16
  1. 2
      samples/TestApplicationShared/GalleryStyle.cs
  2. 1
      samples/TestApplicationShared/MainWindow.cs
  3. 4
      src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs
  4. 53
      src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs
  5. 82
      src/Markup/Perspex.Markup.Xaml/Data/Binding.cs
  6. 35
      src/Markup/Perspex.Markup.Xaml/Data/IXamlBinding.cs
  7. 26
      src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs
  8. 1
      src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  9. 1
      src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
  10. 1
      src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
  11. 25
      src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs
  12. 8
      src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs
  13. 2
      src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs
  14. 1
      src/Perspex.Animation/Animatable.cs
  15. 5
      src/Perspex.Animation/Animate.cs
  16. 20
      src/Perspex.Base/Data/AssignBindingAttribute.cs
  17. 36
      src/Perspex.Base/Data/BindingMode.cs
  18. 2
      src/Perspex.Base/Data/BindingPriority.cs
  19. 33
      src/Perspex.Base/Data/IBinding.cs
  20. 45
      src/Perspex.Base/Data/IndexerDescriptor.cs
  21. 2
      src/Perspex.Base/Diagnostics/PerspexObjectExtensions.cs
  22. 2
      src/Perspex.Base/Diagnostics/PerspexPropertyValue.cs
  23. 95
      src/Perspex.Base/IObservablePropertyBag.cs
  24. 61
      src/Perspex.Base/IPerspexObject.cs
  25. 10
      src/Perspex.Base/Perspex.Base.csproj
  26. 104
      src/Perspex.Base/PerspexObject.cs
  27. 222
      src/Perspex.Base/PerspexObjectExtensions.cs
  28. 19
      src/Perspex.Base/PerspexProperty.cs
  29. 2
      src/Perspex.Base/PerspexPropertyChangedEventArgs.cs
  30. 1
      src/Perspex.Base/PerspexProperty`1.cs
  31. 1
      src/Perspex.Controls/Control.cs
  32. 31
      src/Perspex.Controls/ControlExtensions.cs
  33. 61
      src/Perspex.Controls/DropDown.cs
  34. 4
      src/Perspex.Controls/Presenters/ScrollContentPresenter.cs
  35. 8
      src/Perspex.Controls/Presenters/TextPresenter.cs
  36. 2
      src/Perspex.Controls/Primitives/AccessText.cs
  37. 9
      src/Perspex.Controls/Primitives/ScrollBar.cs
  38. 1
      src/Perspex.Controls/Primitives/SelectingItemsControl.cs
  39. 5
      src/Perspex.Controls/Primitives/TemplatedControl.cs
  40. 2
      src/Perspex.Controls/Primitives/Track.cs
  41. 2
      src/Perspex.Controls/RadioButton.cs
  42. 10
      src/Perspex.Controls/ScrollViewer.cs
  43. 9
      src/Perspex.Controls/TextBlock.cs
  44. 5
      src/Perspex.Controls/TextBox.cs
  45. 4
      src/Perspex.Controls/TopLevel.cs
  46. 1
      src/Perspex.Diagnostics/Debug.cs
  47. 2
      src/Perspex.Diagnostics/DevTools.cs
  48. 1
      src/Perspex.Diagnostics/ViewModels/PropertyDetails.cs
  49. 2
      src/Perspex.Diagnostics/Views/ControlDetailsView.cs
  50. 2
      src/Perspex.Diagnostics/Views/LogicalTreeView.cs
  51. 2
      src/Perspex.Diagnostics/Views/VisualTreeView.cs
  52. 4
      src/Perspex.SceneGraph/Animation/CrossFade.cs
  53. 2
      src/Perspex.SceneGraph/Media/MatrixTransform.cs
  54. 2
      src/Perspex.SceneGraph/Media/RotateTransform.cs
  55. 4
      src/Perspex.SceneGraph/Media/TranslateTransform.cs
  56. 1
      src/Perspex.SceneGraph/Visual.cs
  57. 1
      src/Perspex.Styling/Perspex.Styling.csproj
  58. 2
      src/Perspex.Styling/Styling/IStyleable.cs
  59. 9
      src/Perspex.Styling/Styling/ITemplatedControl.cs
  60. 65
      src/Perspex.Styling/Styling/ObservableSetter.cs
  61. 2
      src/Perspex.Styling/Styling/Selectors.cs
  62. 48
      src/Perspex.Styling/Styling/Setter.cs
  63. 6
      src/Perspex.Themes.Default/DropDown.paml
  64. 2
      src/Perspex.Themes.Default/DropDownItem.paml
  65. 1
      tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj
  66. 7
      tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs
  67. 1
      tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs
  68. 49
      tests/Perspex.Base.UnitTests/PerspexObjectTests_GetSubject.cs
  69. 1
      tests/Perspex.Base.UnitTests/PerspexObjectTests_SetValue.cs
  70. 1
      tests/Perspex.Base.UnitTests/PerspexPropertyTests.cs
  71. 2
      tests/Perspex.Controls.UnitTests/DropDownTests.cs
  72. 7
      tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  73. 3
      tests/Perspex.LeakTests/ControlTests.cs
  74. 134
      tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs
  75. 8
      tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs
  76. 45
      tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
  77. 7
      tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs
  78. 5
      tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj
  79. 33
      tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs
  80. 34
      tests/Perspex.Markup.Xaml.UnitTests/Templates/TreeDataTemplateTests.cs
  81. 1
      tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj
  82. 54
      tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs
  83. 53
      tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs
  84. 28
      tests/Perspex.Styling.UnitTests/SetterTests.cs
  85. 46
      tests/Perspex.Styling.UnitTests/StyleTests.cs
  86. 54
      tests/Perspex.Styling.UnitTests/TestControlBase.cs
  87. 58
      tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs

2
samples/TestApplicationShared/GalleryStyle.cs

@ -23,7 +23,7 @@ namespace TestApplication
{
Setters = new[]
{
new Setter (TemplatedControl.TemplateProperty, new FuncControlTemplate<TabControl> (TabControlTemplate))
new Setter (TemplatedControl.TemplateProperty, new FuncControlTemplate<TabControl>(TabControlTemplate))
}
},

1
samples/TestApplicationShared/MainWindow.cs

@ -11,6 +11,7 @@ using Perspex.Controls.Html;
using Perspex.Controls.Primitives;
using Perspex.Controls.Shapes;
using Perspex.Controls.Templates;
using Perspex.Data;
using Perspex.Diagnostics;
using Perspex.Layout;
using Perspex.Media;

4
src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs

@ -15,6 +15,7 @@ using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using Perspex.Input;
using Perspex.Markup.Xaml.Converters;
using Perspex.Markup.Xaml.Data;
using Perspex.Media;
using Perspex.Media.Imaging;
using Perspex.Metadata;
@ -58,9 +59,10 @@ namespace Perspex.Markup.Xaml.Context
var forcedAssemblies = new[]
{
typeof(Binding),
typeof(Control),
typeof(Style),
typeof(IValueConverter),
typeof(Style),
}.Select(t => t.GetTypeInfo().Assembly);
foreach (var nsa in

53
src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs

@ -9,6 +9,8 @@ using System.Runtime.CompilerServices;
using Glass;
using OmniXaml.ObjectAssembler;
using OmniXaml.Typing;
using Perspex.Controls;
using Perspex.Data;
using Perspex.Markup.Xaml.Data;
using Perspex.Styling;
@ -39,9 +41,9 @@ namespace Perspex.Markup.Xaml.Context
public override void SetValue(object instance, object value)
{
if (value is IXamlBinding)
if (value is IBinding)
{
HandleBinding(instance, (IXamlBinding)value);
HandleBinding(instance, (IBinding)value);
}
else if (IsPerspexProperty)
{
@ -68,35 +70,38 @@ namespace Perspex.Markup.Xaml.Context
po.SetValue(pp, value);
}
private void HandleBinding(object instance, IXamlBinding binding)
private void HandleBinding(object instance, IBinding binding)
{
if (typeof(IXamlBinding).GetTypeInfo().IsAssignableFrom(_xamlMember.XamlType.UnderlyingType.GetTypeInfo()))
if (!(AssignBinding(instance, binding) || ApplyBinding(instance, binding)))
{
var property = instance.GetType().GetRuntimeProperty(_xamlMember.Name);
throw new InvalidOperationException(
$"Cannot assign to '{_xamlMember.Name}' on '{instance.GetType()}");
}
}
if (property == null || !property.CanWrite)
{
throw new InvalidOperationException(
$"Cannot assign to '{_xamlMember.Name}' on '{instance.GetType()}");
}
private bool AssignBinding(object instance, IBinding binding)
{
var property = instance.GetType()
.GetRuntimeProperties()
.FirstOrDefault(x => x.Name == _xamlMember.Name);
if (property?.GetCustomAttribute<AssignBindingAttribute>() != null)
{
property.SetValue(instance, binding);
return true;
}
else
{
ApplyBinding(instance, binding);
}
return false;
}
private void ApplyBinding(object instance, IXamlBinding binding)
private bool ApplyBinding(object instance, IBinding binding)
{
var perspexObject = instance as PerspexObject;
var targetControl = instance as IControl;
var attached = _xamlMember as PerspexAttachableXamlMember;
if (perspexObject == null)
if (targetControl == null)
{
throw new InvalidOperationException(
$"Cannot bind to an object of type '{instance.GetType()}");
return false;
}
PerspexProperty property;
@ -105,7 +110,7 @@ namespace Perspex.Markup.Xaml.Context
if (attached == null)
{
propertyName = _xamlMember.Name;
property = PerspexPropertyRegistry.Instance.GetRegistered(perspexObject)
property = PerspexPropertyRegistry.Instance.GetRegistered((PerspexObject)targetControl)
.FirstOrDefault(x => x.Name == propertyName);
}
else
@ -115,18 +120,18 @@ namespace Perspex.Markup.Xaml.Context
propertyName = attached.DeclaringType.UnderlyingType.Name + '.' + _xamlMember.Name;
property = PerspexPropertyRegistry.Instance.GetRegistered(perspexObject)
property = PerspexPropertyRegistry.Instance.GetRegistered((PerspexObject)targetControl)
.Where(x => x.IsAttached && x.OwnerType == attached.DeclaringType.UnderlyingType)
.FirstOrDefault(x => x.Name == _xamlMember.Name);
}
if (property == null)
{
throw new InvalidOperationException(
$"Cannot find '{propertyName}' on '{instance.GetType()}");
return false;
}
binding.Bind(perspexObject, property);
targetControl.Bind(property, binding);
return true;
}
private bool ValueRequiresSpecialHandling(object value)

82
src/Markup/Perspex.Markup.Xaml/Data/Binding.cs

@ -6,6 +6,7 @@ using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Perspex.Controls;
using Perspex.Data;
using Perspex.Markup.Data;
namespace Perspex.Markup.Xaml.Data
@ -13,7 +14,7 @@ namespace Perspex.Markup.Xaml.Data
/// <summary>
/// A XAML binding.
/// </summary>
public class Binding : IXamlBinding
public class Binding : IBinding
{
/// <summary>
/// Gets or sets the <see cref="IValueConverter"/> to use.
@ -50,48 +51,23 @@ namespace Perspex.Markup.Xaml.Data
/// </summary>
public string Path { get; set; }
/// <summary>
/// Applies the binding to a property on an instance.
/// </summary>
/// <param name="instance">The target instance.</param>
/// <param name="property">The target property.</param>
public void Bind(IObservablePropertyBag instance, PerspexProperty property)
{
Contract.Requires<ArgumentNullException>(instance != null);
Contract.Requires<ArgumentNullException>(property != null);
var subject = CreateSubject(
instance,
property.PropertyType,
property == Control.DataContextProperty);
if (subject != null)
{
Bind(instance, property, subject);
}
}
/// <summary>
/// Creates a subject that can be used to get and set the value of the binding.
/// </summary>
/// <param name="target">The target instance.</param>
/// <param name="targetType">The type of the target property.</param>
/// <param name="targetIsDataContext">
/// Whether the target property is the DataContext property.
/// </param>
/// <returns>An <see cref="ISubject{object}"/>.</returns>
/// <param name="targetProperty">The target property. May be null.</param>
/// <returns>An <see cref="ISubject{Object}"/>.</returns>
public ISubject<object> CreateSubject(
IObservablePropertyBag target,
Type targetType,
bool targetIsDataContext = false)
IPerspexObject target,
PerspexProperty targetProperty)
{
Contract.Requires<ArgumentNullException>(target != null);
Contract.Requires<ArgumentNullException>(targetType != null);
var pathInfo = ParsePath(Path);
ValidateState(pathInfo);
ExpressionObserver observer;
var targetIsDataContext = targetProperty == Control.DataContextProperty;
if (pathInfo.ElementName != null || ElementName != null)
{
@ -120,47 +96,11 @@ namespace Perspex.Markup.Xaml.Data
return new ExpressionSubject(
observer,
targetType,
targetProperty?.PropertyType ?? typeof(object),
Converter ?? DefaultValueConverter.Instance,
ConverterParameter);
}
/// <summary>
/// Applies a binding subject to a property on an instance.
/// </summary>
/// <param name="target">The target instance.</param>
/// <param name="property">The target property.</param>
/// <param name="subject">The binding subject.</param>
internal void Bind(IObservablePropertyBag target, PerspexProperty property, ISubject<object> subject)
{
Contract.Requires<ArgumentNullException>(target != null);
Contract.Requires<ArgumentNullException>(property != null);
Contract.Requires<ArgumentNullException>(subject != null);
var mode = Mode == BindingMode.Default ?
property.DefaultBindingMode : Mode;
switch (mode)
{
case BindingMode.Default:
case BindingMode.OneWay:
target.Bind(property, subject, Priority);
break;
case BindingMode.TwoWay:
target.BindTwoWay(property, subject, Priority);
break;
case BindingMode.OneTime:
target.GetObservable(Control.DataContextProperty).Subscribe(dataContext =>
{
subject.Take(1).Subscribe(x => target.SetValue(property, x, Priority));
});
break;
case BindingMode.OneWayToSource:
target.GetObservable(property).Subscribe(subject);
break;
}
}
private static PathInfo ParsePath(string path)
{
var result = new PathInfo();
@ -209,7 +149,7 @@ namespace Perspex.Markup.Xaml.Data
}
private ExpressionObserver CreateDataContextSubject(
IObservablePropertyBag target,
IPerspexObject target,
string path,
bool targetIsDataContext)
{
@ -231,7 +171,7 @@ namespace Perspex.Markup.Xaml.Data
{
return new ExpressionObserver(
target.GetObservable(Visual.VisualParentProperty)
.OfType<IObservablePropertyBag>()
.OfType<IPerspexObject>()
.Select(x => x.GetObservable(Control.DataContextProperty))
.Switch(),
path);
@ -239,7 +179,7 @@ namespace Perspex.Markup.Xaml.Data
}
private ExpressionObserver CreateTemplatedParentSubject(
IObservablePropertyBag target,
IPerspexObject target,
string path)
{
Contract.Requires<ArgumentNullException>(target != null);

35
src/Markup/Perspex.Markup.Xaml/Data/IXamlBinding.cs

@ -1,35 +0,0 @@
// 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.Reactive.Subjects;
namespace Perspex.Markup.Xaml.Data
{
/// <summary>
/// Defines a binding that can be created in XAML markup.
/// </summary>
public interface IXamlBinding
{
/// <summary>
/// Applies the binding to a property on an instance.
/// </summary>
/// <param name="instance">The target instance.</param>
/// <param name="property">The target property.</param>
void Bind(IObservablePropertyBag instance, PerspexProperty property);
/// <summary>
/// Creates a subject that can be used to get and set the value of the binding.
/// </summary>
/// <param name="target">The target instance.</param>
/// <param name="targetType">The type of the target property.</param>
/// <param name="targetIsDataContext">
/// Whether the target property is the DataContext property.
/// </param>
/// <returns>An <see cref="ISubject{object}"/>.</returns>
ISubject<object> CreateSubject(
IObservablePropertyBag target,
Type targetType,
bool targetIsDataContext = false);
}
}

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

@ -8,6 +8,7 @@ using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Perspex.Controls;
using Perspex.Data;
using Perspex.Metadata;
namespace Perspex.Markup.Xaml.Data
@ -15,13 +16,13 @@ namespace Perspex.Markup.Xaml.Data
/// <summary>
/// A XAML binding that calculates an aggregate value from multiple child <see cref="Bindings"/>.
/// </summary>
public class MultiBinding : IXamlBinding
public class MultiBinding : IBinding
{
/// <summary>
/// Gets the collection of child bindings.
/// </summary>
[Content]
public IList<IXamlBinding> Bindings { get; set; } = new List<IXamlBinding>();
public IList<IBinding> Bindings { get; set; } = new List<IBinding>();
/// <summary>
/// Gets or sets the <see cref="IValueConverter"/> to use.
@ -48,9 +49,9 @@ namespace Perspex.Markup.Xaml.Data
/// </summary>
/// <param name="instance">The target instance.</param>
/// <param name="property">The target property.</param>
public void Bind(IObservablePropertyBag instance, PerspexProperty property)
public void Bind(IPerspexObject instance, PerspexProperty property)
{
var subject = CreateSubject(instance, property.PropertyType);
var subject = CreateSubject(instance, property);
if (subject != null)
{
@ -62,23 +63,18 @@ namespace Perspex.Markup.Xaml.Data
/// Creates a subject that can be used to get and set the value of the binding.
/// </summary>
/// <param name="target">The target instance.</param>
/// <param name="targetType">The type of the target property.</param>
/// <param name="targetIsDataContext">
/// Whether the target property is the DataContext property.
/// </param>
/// <returns>An <see cref="ISubject{object}"/>.</returns>
public ISubject<object> CreateSubject(
IObservablePropertyBag target,
Type targetType,
bool targetIsDataContext = false)
/// <param name="targetProperty">The target property.</param>
/// <returns>An <see cref="ISubject{Object}"/>.</returns>
public ISubject<object> CreateSubject(IPerspexObject target, PerspexProperty targetProperty)
{
if (Converter == null)
{
throw new NotSupportedException("MultiBinding without Converter not currently supported.");
}
var targetType = targetProperty?.PropertyType ?? typeof(object);
var result = new BehaviorSubject<object>(PerspexProperty.UnsetValue);
var children = Bindings.Select(x => x.CreateSubject(target, typeof(object)));
var children = Bindings.Select(x => x.CreateSubject(target, null));
var input = children.CombineLatest().Select(x =>
Converter.Convert(x, targetType, null, CultureInfo.CurrentUICulture));
input.Subscribe(result);
@ -91,7 +87,7 @@ namespace Perspex.Markup.Xaml.Data
/// <param name="target">The target instance.</param>
/// <param name="property">The target property.</param>
/// <param name="subject">The binding subject.</param>
internal void Bind(IObservablePropertyBag target, PerspexProperty property, ISubject<object> subject)
internal void Bind(IPerspexObject target, PerspexProperty property, ISubject<object> subject)
{
var mode = Mode == BindingMode.Default ?
property.DefaultBindingMode : Mode;

1
src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using OmniXaml;
using Perspex.Data;
using Perspex.Markup.Xaml.Data;
namespace Perspex.Markup.Xaml.MarkupExtensions

1
src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using OmniXaml;
using Perspex.Data;
using Perspex.Markup.Xaml.Data;
namespace Perspex.Markup.Xaml.MarkupExtensions

1
src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj

@ -42,7 +42,6 @@
<Compile Include="Converters\CursorTypeConverter.cs" />
<Compile Include="Converters\PerspexListTypeConverter.cs" />
<Compile Include="Converters\RelativeRectTypeConverter.cs" />
<Compile Include="Data\IXamlBinding.cs" />
<Compile Include="Data\MultiBinding.cs" />
<Compile Include="Data\RelativeSource.cs" />
<Compile Include="Data\SourceBindingEndpoint.cs" />

25
src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using OmniXaml;
using Perspex.Markup.Xaml.Context;
using Perspex.Platform;
@ -39,6 +40,8 @@ namespace Perspex.Markup.Xaml
/// <param name="obj">The object to load the XAML into.</param>
public static void Load(object obj)
{
Contract.Requires<ArgumentNullException>(obj != null);
var loader = new PerspexXamlLoader();
loader.Load(obj.GetType(), obj);
}
@ -53,6 +56,8 @@ namespace Perspex.Markup.Xaml
/// <returns>The loaded object.</returns>
public object Load(Type type, object rootInstance = null)
{
Contract.Requires<ArgumentNullException>(type != null);
// HACK: Currently Visual Studio is forcing us to change the extension of xaml files
// in certain situations, so we try to load .xaml and if that's not found we try .paml.
// Ideally we'd be able to use .xaml everywhere
@ -85,6 +90,8 @@ namespace Perspex.Markup.Xaml
/// <returns>The loaded object.</returns>
public object Load(Uri uri, object rootInstance = null)
{
Contract.Requires<ArgumentNullException>(uri != null);
var assetLocator = PerspexLocator.Current.GetService<IAssetLoader>();
if (assetLocator == null)
@ -99,6 +106,24 @@ namespace Perspex.Markup.Xaml
}
}
/// <summary>
/// Loads XAML from a string.
/// </summary>
/// <param name="xaml">The string containing the XAML.</param>
/// <param name="rootInstance">
/// The optional instance into which the XAML should be loaded.
/// </param>
/// <returns>The loaded object.</returns>
public object Load(string xaml, object rootInstance = null)
{
Contract.Requires<ArgumentNullException>(xaml != null);
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)))
{
return Load(stream, rootInstance);
}
}
/// <summary>
/// Gets the URI for a type.
/// </summary>

8
src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs

@ -20,10 +20,12 @@ namespace Perspex.Markup.Xaml.Templates
{
if (DataType == null)
{
throw new InvalidOperationException("DataTemplate must have a DataType.");
return true;
}
else
{
return DataType.GetTypeInfo().IsAssignableFrom(data.GetType().GetTypeInfo());
}
return DataType.GetTypeInfo().IsAssignableFrom(data.GetType().GetTypeInfo());
}
public IControl Build(object data)

2
src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs

@ -6,6 +6,7 @@ using System.Collections;
using System.Reactive.Linq;
using Perspex.Controls;
using Perspex.Controls.Templates;
using Perspex.Data;
using Perspex.Markup.Data;
using Perspex.Markup.Xaml.Data;
using Perspex.Metadata;
@ -19,6 +20,7 @@ namespace Perspex.Markup.Xaml.Templates
[Content]
public TemplateContent Content { get; set; }
[AssignBinding]
public Binding ItemsSource { get; set; }
public bool Match(object data)

1
src/Perspex.Animation/Animatable.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Linq;
using Perspex.Data;
namespace Perspex.Animation
{

5
src/Perspex.Animation/Animate.cs

@ -5,6 +5,7 @@ using System;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using Perspex.Data;
using Perspex.Threading;
namespace Perspex.Animation
@ -96,7 +97,7 @@ namespace Perspex.Animation
/// <param name="duration">The duration of the animation.</param>
/// <returns>An <see cref="Animation"/> that can be used to track or stop the animation.</returns>
public static Animation Property(
IObservablePropertyBag target,
IPerspexObject target,
PerspexProperty property,
object start,
object finish,
@ -119,7 +120,7 @@ namespace Perspex.Animation
/// <param name="duration">The duration of the animation.</param>
/// <returns>An <see cref="Animation"/> that can be used to track or stop the animation.</returns>
public static Animation<T> Property<T>(
IObservablePropertyBag target,
IPerspexObject target,
PerspexProperty<T> property,
T start,
T finish,

20
src/Perspex.Base/Data/AssignBindingAttribute.cs

@ -0,0 +1,20 @@
// 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.Data
{
/// <summary>
/// Signifies that a binding can be assigned to a property.
/// </summary>
/// <remarks>
/// Usually in markup, when a binding is set for a property that property will be bound.
/// Applying this attribute to a property indicates that the binding should be assigned to
/// the property rather than bound.
/// </remarks>
[AttributeUsage(AttributeTargets.Property)]
public sealed class AssignBindingAttribute : Attribute
{
}
}

36
src/Perspex.Base/Data/BindingMode.cs

@ -0,0 +1,36 @@
// 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.Data
{
/// <summary>
/// Defines possible binding modes.
/// </summary>
public enum BindingMode
{
/// <summary>
/// Uses the default binding mode specified for the property.
/// </summary>
Default,
/// <summary>
/// Binds one way from source to target.
/// </summary>
OneWay,
/// <summary>
/// Binds two-way with the initial value coming from the target.
/// </summary>
TwoWay,
/// <summary>
/// Updates the target when the application starts or when the data context changes.
/// </summary>
OneTime,
/// <summary>
/// Binds one way from target to source.
/// </summary>
OneWayToSource,
}
}

2
src/Perspex.Base/BindingPriority.cs → src/Perspex.Base/Data/BindingPriority.cs

@ -1,7 +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.
namespace Perspex
namespace Perspex.Data
{
/// <summary>
/// The priority of a binding.

33
src/Perspex.Base/Data/IBinding.cs

@ -0,0 +1,33 @@
// 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.Reactive.Subjects;
namespace Perspex.Data
{
/// <summary>
/// Holds a binding that can be applied to a property on an object.
/// </summary>
public interface IBinding
{
/// <summary>
/// Gets the binding mode.
/// </summary>
BindingMode Mode { get; }
/// <summary>
/// Gets the binding priority.
/// </summary>
BindingPriority Priority { get; }
/// <summary>
/// Creates a subject that can be used to get and set the value of the binding.
/// </summary>
/// <param name="target">The target instance.</param>
/// <param name="targetProperty">The target property. May be null.</param>
/// <returns>An <see cref="ISubject{Object}"/>.</returns>
ISubject<object> CreateSubject(
IPerspexObject target,
PerspexProperty targetProperty);
}
}

45
src/Perspex.Base/BindingDescriptor.cs → src/Perspex.Base/Data/IndexerDescriptor.cs

@ -4,43 +4,12 @@
using System;
using System.Reactive;
namespace Perspex
namespace Perspex.Data
{
/// <summary>
/// Defines possible binding modes.
/// Holds a description of a binding for <see cref="PerspexObject"/>'s [] operator.
/// </summary>
public enum BindingMode
{
/// <summary>
/// Uses the default binding mode specified for the property.
/// </summary>
Default,
/// <summary>
/// Binds one way from source to target.
/// </summary>
OneWay,
/// <summary>
/// Binds two-way with the initial value coming from the target.
/// </summary>
TwoWay,
/// <summary>
/// Updates the target when the application starts or when the data context changes.
/// </summary>
OneTime,
/// <summary>
/// Binds one way from target to source.
/// </summary>
OneWayToSource,
}
/// <summary>
/// Holds a description of a binding, usually for <see cref="PerspexObject"/>'s [] operator.
/// </summary>
public class BindingDescriptor : ObservableBase<object>, IDescription
public class IndexerDescriptor : ObservableBase<object>, IDescription
{
/// <summary>
/// Gets or sets the binding mode.
@ -100,7 +69,7 @@ namespace Perspex
/// </summary>
/// <param name="binding">The current binding.</param>
/// <returns>A two-way binding.</returns>
public static BindingDescriptor operator !(BindingDescriptor binding)
public static IndexerDescriptor operator !(IndexerDescriptor binding)
{
return binding.WithMode(BindingMode.TwoWay);
}
@ -110,7 +79,7 @@ namespace Perspex
/// </summary>
/// <param name="binding">The current binding.</param>
/// <returns>A two-way binding.</returns>
public static BindingDescriptor operator ~(BindingDescriptor binding)
public static IndexerDescriptor operator ~(IndexerDescriptor binding)
{
return binding.WithMode(BindingMode.TwoWay);
}
@ -120,7 +89,7 @@ namespace Perspex
/// </summary>
/// <param name="mode">The binding mode.</param>
/// <returns>The object that the method was called on.</returns>
public BindingDescriptor WithMode(BindingMode mode)
public IndexerDescriptor WithMode(BindingMode mode)
{
Mode = mode;
return this;
@ -131,7 +100,7 @@ namespace Perspex
/// </summary>
/// <param name="priority">The binding priority.</param>
/// <returns>The object that the method was called on.</returns>
public BindingDescriptor WithPriority(BindingPriority priority)
public IndexerDescriptor WithPriority(BindingPriority priority)
{
Priority = priority;
return this;

2
src/Perspex.Base/Diagnostics/PerspexObjectExtensions.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 Perspex.Data;
namespace Perspex.Diagnostics
{
/// <summary>

2
src/Perspex.Base/Diagnostics/PerspexPropertyValue.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 Perspex.Data;
namespace Perspex.Diagnostics
{
/// <summary>

95
src/Perspex.Base/IObservablePropertyBag.cs

@ -1,95 +0,0 @@
// 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.Reactive.Subjects;
namespace Perspex
{
/// <summary>
/// Interface for getting/setting <see cref="PerspexProperty"/> bindings on an object.
/// </summary>
public interface IObservablePropertyBag : IPropertyBag
{
/// <summary>
/// Binds a <see cref="PerspexProperty"/> to an observable.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
IDisposable Bind(
PerspexProperty property,
IObservable<object> source,
BindingPriority priority = BindingPriority.LocalValue);
/// <summary>
/// Binds a <see cref="PerspexProperty"/> to an observable.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
IDisposable Bind<T>(
PerspexProperty<T> property,
IObservable<T> source,
BindingPriority priority = BindingPriority.LocalValue);
/// <summary>
/// Initiates a two-way binding between <see cref="PerspexProperty"/>s.
/// </summary>
/// <param name="property">The property on this object.</param>
/// <param name="source">The source object.</param>
/// <param name="sourceProperty">The property on the source object.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
/// <remarks>
/// The binding is first carried out from <paramref name="source"/> to this.
/// </remarks>
IDisposable BindTwoWay(
PerspexProperty property,
PerspexObject source,
PerspexProperty sourceProperty,
BindingPriority priority = BindingPriority.LocalValue);
/// <summary>
/// Initiates a two-way binding between a <see cref="PerspexProperty"/> and an
/// <see cref="ISubject{Object}"/>.
/// </summary>
/// <param name="property">The property on this object.</param>
/// <param name="source">The subject to bind to.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
/// <remarks>
/// The binding is first carried out from <paramref name="source"/> to this.
/// </remarks>
IDisposable BindTwoWay(
PerspexProperty property,
ISubject<object> source,
BindingPriority priority = BindingPriority.LocalValue);
/// <summary>
/// Gets an observable for a <see cref="PerspexProperty"/>.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>An observable.</returns>
IObservable<object> GetObservable(PerspexProperty property);
/// <summary>
/// Gets an observable for a <see cref="PerspexProperty"/>.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <returns>An observable.</returns>
IObservable<T> GetObservable<T>(PerspexProperty<T> property);
}
}

61
src/Perspex.Base/IPropertyBag.cs → src/Perspex.Base/IPerspexObject.cs

@ -1,23 +1,20 @@
// 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>
/// Interface for getting/setting <see cref="PerspexProperty"/> values on an object.
/// </summary>
public interface IPropertyBag
public interface IPerspexObject
{
/// <summary>
/// Gets the object that inherited <see cref="PerspexProperty"/> values are inherited from.
/// Raised when a <see cref="PerspexProperty"/> value changes on this object.
/// </summary>
IPropertyBag InheritanceParent { get; }
/// <summary>
/// Clears a <see cref="PerspexProperty"/>'s local value.
/// </summary>
/// <param name="property">The property.</param>
void ClearValue(PerspexProperty property);
event EventHandler<PerspexPropertyChangedEventArgs> PropertyChanged;
/// <summary>
/// Gets a <see cref="PerspexProperty"/> value.
@ -34,13 +31,6 @@ namespace Perspex
/// <returns>The value.</returns>
T GetValue<T>(PerspexProperty<T> property);
/// <summary>
/// Checks whether a <see cref="PerspexProperty"/> is registered on this object.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>True if the property is registered, otherwise false.</returns>
bool IsRegistered(PerspexProperty property);
/// <summary>
/// Checks whether a <see cref="PerspexProperty"/> is set on this object.
/// </summary>
@ -54,7 +44,10 @@ namespace Perspex
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
/// <param name="priority">The priority of the value.</param>
void SetValue(PerspexProperty property, object value, BindingPriority priority = BindingPriority.LocalValue);
void SetValue(
PerspexProperty property,
object value,
BindingPriority priority = BindingPriority.LocalValue);
/// <summary>
/// Sets a <see cref="PerspexProperty"/> value.
@ -63,6 +56,38 @@ namespace Perspex
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
/// <param name="priority">The priority of the value.</param>
void SetValue<T>(PerspexProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue);
void SetValue<T>(
PerspexProperty<T> property,
T value,
BindingPriority priority = BindingPriority.LocalValue);
/// <summary>
/// Binds a <see cref="PerspexProperty"/> to an observable.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
IDisposable Bind(
PerspexProperty property,
IObservable<object> source,
BindingPriority priority = BindingPriority.LocalValue);
/// <summary>
/// Binds a <see cref="PerspexProperty"/> to an observable.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
IDisposable Bind<T>(
PerspexProperty<T> property,
IObservable<T> source,
BindingPriority priority = BindingPriority.LocalValue);
}
}

10
src/Perspex.Base/Perspex.Base.csproj

@ -42,11 +42,13 @@
<Compile Include="..\Shared\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="BindingDescriptor.cs" />
<Compile Include="Data\AssignBindingAttribute.cs" />
<Compile Include="Data\IBinding.cs" />
<Compile Include="Data\IndexerDescriptor.cs" />
<Compile Include="Collections\PerspexDictionary.cs" />
<Compile Include="Data\BindingMode.cs" />
<Compile Include="Diagnostics\PerspexObjectExtensions.cs" />
<Compile Include="IObservablePropertyBag.cs" />
<Compile Include="IPropertyBag.cs" />
<Compile Include="IPerspexObject.cs" />
<Compile Include="Metadata\ContentAttribute.cs" />
<Compile Include="PerspexDisposable.cs" />
<Compile Include="PerspexLocator.cs" />
@ -56,7 +58,7 @@
<Compile Include="PerspexProperty`1.cs" />
<Compile Include="Platform\IAssetLoader.cs" />
<Compile Include="Platform\IPclPlatformWrapper.cs" />
<Compile Include="BindingPriority.cs" />
<Compile Include="Data\BindingPriority.cs" />
<Compile Include="PriorityBindingEntry.cs" />
<Compile Include="Collections\IPerspexList.cs" />
<Compile Include="Collections\PerspexList.cs" />

104
src/Perspex.Base/PerspexObject.cs

@ -8,8 +8,7 @@ using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Reflection;
using Perspex.Reactive;
using Perspex.Data;
using Perspex.Threading;
using Perspex.Utilities;
using Serilog;
@ -23,7 +22,7 @@ namespace Perspex
/// <remarks>
/// This class is analogous to DependencyObject in WPF.
/// </remarks>
public class PerspexObject : IObservablePropertyBag, INotifyPropertyChanged
public class PerspexObject : IPerspexObject, INotifyPropertyChanged
{
/// <summary>
/// The parent object that inherited values are inherited from.
@ -89,11 +88,6 @@ namespace Perspex
remove { _inpcChanged -= value; }
}
/// <summary>
/// Gets the object that inherited <see cref="PerspexProperty"/> values are inherited from.
/// </summary>
IPropertyBag IPropertyBag.InheritanceParent => InheritanceParent;
/// <summary>
/// Gets or sets the parent object that inherited <see cref="PerspexProperty"/> values
/// are inherited from.
@ -159,7 +153,7 @@ namespace Perspex
/// Gets or sets a binding for a <see cref="PerspexProperty"/>.
/// </summary>
/// <param name="binding">The binding information.</param>
public IObservable<object> this[BindingDescriptor binding]
public IObservable<object> this[IndexerDescriptor binding]
{
get
{
@ -171,7 +165,7 @@ namespace Perspex
var mode = (binding.Mode == BindingMode.Default) ?
binding.Property.DefaultBindingMode :
binding.Mode;
var sourceBinding = value as BindingDescriptor;
var sourceBinding = value as IndexerDescriptor;
if (sourceBinding == null && mode > BindingMode.OneWay)
{
@ -188,7 +182,7 @@ namespace Perspex
SetValue(binding.Property, sourceBinding.Source.GetValue(sourceBinding.Property), binding.Priority);
break;
case BindingMode.OneWayToSource:
sourceBinding.Source.Bind(sourceBinding.Property, GetObservable(binding.Property), binding.Priority);
sourceBinding.Source.Bind(sourceBinding.Property, this.GetObservable(binding.Property), binding.Priority);
break;
case BindingMode.TwoWay:
BindTwoWay(binding.Property, sourceBinding.Source, sourceBinding.Property);
@ -197,9 +191,9 @@ namespace Perspex
}
}
protected virtual BindingDescriptor CreateBindingDescriptor(BindingDescriptor source)
protected virtual IndexerDescriptor CreateBindingDescriptor(IndexerDescriptor source)
{
return new BindingDescriptor
return new IndexerDescriptor
{
Mode = source.Mode,
Priority = source.Priority,
@ -223,81 +217,6 @@ namespace Perspex
SetValue(property, PerspexProperty.UnsetValue);
}
/// <summary>
/// Gets an observable for a <see cref="PerspexProperty"/>.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>An observable.</returns>
public IObservable<object> GetObservable(PerspexProperty property)
{
Contract.Requires<ArgumentNullException>(property != null);
return new PerspexObservable<object>(
observer =>
{
EventHandler<PerspexPropertyChangedEventArgs> handler = (s, e) =>
{
if (e.Property == property)
{
observer.OnNext(e.NewValue);
}
};
observer.OnNext(GetValue(property));
PropertyChanged += handler;
return Disposable.Create(() =>
{
PropertyChanged -= handler;
});
},
GetDescription(property));
}
/// <summary>
/// Gets an observable for a <see cref="PerspexProperty"/>.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
/// <param name="property">The property.</param>
/// <returns>An observable.</returns>
public IObservable<T> GetObservable<T>(PerspexProperty<T> property)
{
Contract.Requires<ArgumentNullException>(property != null);
return GetObservable((PerspexProperty)property).Cast<T>();
}
/// <summary>
/// Gets an observable for a <see cref="PerspexProperty"/>.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <returns>An observable which when subscribed pushes the old and new values of the
/// property each time it is changed.</returns>
public IObservable<Tuple<T, T>> GetObservableWithHistory<T>(PerspexProperty<T> property)
{
return new PerspexObservable<Tuple<T, T>>(
observer =>
{
EventHandler<PerspexPropertyChangedEventArgs> handler = (s, e) =>
{
if (e.Property == property)
{
observer.OnNext(Tuple.Create((T)e.OldValue, (T)e.NewValue));
}
};
PropertyChanged += handler;
return Disposable.Create(() =>
{
PropertyChanged -= handler;
});
},
GetDescription(property));
}
/// <summary>
/// Gets a <see cref="PerspexProperty"/> value.
/// </summary>
@ -589,7 +508,7 @@ namespace Perspex
return new CompositeDisposable(
Bind(property, source.GetObservable(sourceProperty)),
source.Bind(sourceProperty, GetObservable(property)));
source.Bind(sourceProperty, this.GetObservable(property)));
}
/// <summary>
@ -619,7 +538,7 @@ namespace Perspex
return new CompositeDisposable(
Bind(property, source),
GetObservable(property).Subscribe(source));
this.GetObservable(property).Subscribe(source));
}
/// <summary>
@ -638,11 +557,6 @@ namespace Perspex
}
/// <inheritdoc/>
bool IPropertyBag.IsRegistered(PerspexProperty property)
{
return PerspexPropertyRegistry.Instance.IsRegistered(this, property);
}
/// <summary>
/// Gets all priority values set on the object.
/// </summary>

222
src/Perspex.Base/PerspexObjectExtensions.cs

@ -2,8 +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;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Perspex.Data;
using Perspex.Reactive;
namespace Perspex
{
@ -12,6 +16,162 @@ namespace Perspex
/// </summary>
public static class PerspexObjectExtensions
{
/// <summary>
/// Gets an observable for a <see cref="PerspexProperty"/>.
/// </summary>
/// <param name="o">The object.</param>
/// <param name="property">The property.</param>
/// <returns>An observable.</returns>
public static IObservable<object> GetObservable(this IPerspexObject o, PerspexProperty property)
{
Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(property != null);
return new PerspexObservable<object>(
observer =>
{
EventHandler<PerspexPropertyChangedEventArgs> handler = (s, e) =>
{
if (e.Property == property)
{
observer.OnNext(e.NewValue);
}
};
observer.OnNext(o.GetValue(property));
o.PropertyChanged += handler;
return Disposable.Create(() =>
{
o.PropertyChanged -= handler;
});
},
GetDescription(o, property));
}
/// <summary>
/// Gets an observable for a <see cref="PerspexProperty"/>.
/// </summary>
/// <param name="o">The object.</param>
/// <typeparam name="T">The property type.</typeparam>
/// <param name="property">The property.</param>
/// <returns>An observable.</returns>
public static IObservable<T> GetObservable<T>(this IPerspexObject o, PerspexProperty<T> property)
{
Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(property != null);
return o.GetObservable((PerspexProperty)property).Cast<T>();
}
/// <summary>
/// Gets an observable for a <see cref="PerspexProperty"/>.
/// </summary>
/// <param name="o">The object.</param>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <returns>
/// An observable which when subscribed pushes the old and new values of the property each
/// time it is changed.
/// </returns>
public static IObservable<Tuple<T, T>> GetObservableWithHistory<T>(
this IPerspexObject o,
PerspexProperty<T> property)
{
Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(property != null);
return new PerspexObservable<Tuple<T, T>>(
observer =>
{
EventHandler<PerspexPropertyChangedEventArgs> handler = (s, e) =>
{
if (e.Property == property)
{
observer.OnNext(Tuple.Create((T)e.OldValue, (T)e.NewValue));
}
};
o.PropertyChanged += handler;
return Disposable.Create(() =>
{
o.PropertyChanged -= handler;
});
},
GetDescription(o, property));
}
/// <summary>
/// Gets a subject for a <see cref="PerspexProperty"/>.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
/// <param name="o">The object.</param>
/// <param name="property">The property.</param>
/// <param name="priority">
/// The priority with which binding values are written to the object.
/// </param>
/// <returns>
/// An <see cref="ISubject{T}"/> which can be used for two-way binding to/from the
/// property.
/// </returns>
public static ISubject<T> GetSubject<T>(
this IPerspexObject o,
PerspexProperty<T> property,
BindingPriority priority = BindingPriority.LocalValue)
{
// TODO: Subject.Create<T> is not yet in stable Rx : once it is, remove the
// AnonymousSubject classes from this file and use Subject.Create<T>.
var output = new Subject<T>();
var result = new AnonymousSubject<T>(
Observer.Create<T>(
x => output.OnNext(x),
e => output.OnError(e),
() => output.OnCompleted()),
o.GetObservable(property));
o.Bind(property, output, priority);
return result;
}
/// <summary>
/// Binds a property to a subject according to a <see cref="BindingMode"/>.
/// </summary>
/// <param name="o">The object.</param>
/// <param name="property">The property to bind.</param>
/// <param name="source">The binding source.</param>
/// <param name="mode">The binding mode.</param>
/// <param name="priority">The binding priority.</param>
/// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns>
public static IDisposable Bind(
this IPerspexObject o,
PerspexProperty property,
ISubject<object> source,
BindingMode mode,
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(property != null);
Contract.Requires<ArgumentNullException>(source != null);
switch (mode)
{
case BindingMode.Default:
case BindingMode.OneWay:
return o.Bind(property, source, priority);
case BindingMode.TwoWay:
return new CompositeDisposable(
o.Bind(property, source, priority),
o.GetObservable(property).Subscribe(source));
case BindingMode.OneTime:
return source.Take(1).Subscribe(x => o.SetValue(property, x, priority));
case BindingMode.OneWayToSource:
return o.GetObservable(property).Subscribe(source);
default:
throw new ArgumentException("Invalid binding mode.");
}
}
/// <summary>
/// Subscribes to a property changed notifications for changes that originate from a
/// <typeparamref name="TTarget"/>.
@ -52,6 +212,17 @@ namespace Perspex
return observable.Subscribe(e => SubscribeAdapter(e, handler));
}
/// <summary>
/// Gets a description of a property that van be used in observables.
/// </summary>
/// <param name="o">The object.</param>
/// <param name="property">The property</param>
/// <returns>The description.</returns>
private static string GetDescription(IPerspexObject o, PerspexProperty property)
{
return $"{o.GetType().Name}.{property.Name}";
}
/// <summary>
/// Observer method for <see cref="AddClassHandler{TTarget}(IObservable{PerspexPropertyChangedEventArgs},
/// Func{TTarget, Action{PerspexPropertyChangedEventArgs}})"/>.
@ -71,5 +242,54 @@ namespace Perspex
handler(target)(e);
}
}
class AnonymousSubject<T, U> : ISubject<T, U>
{
private readonly IObserver<T> _observer;
private readonly IObservable<U> _observable;
public AnonymousSubject(IObserver<T> observer, IObservable<U> observable)
{
_observer = observer;
_observable = observable;
}
public void OnCompleted()
{
_observer.OnCompleted();
}
public void OnError(Exception error)
{
if (error == null)
throw new ArgumentNullException("error");
_observer.OnError(error);
}
public void OnNext(T value)
{
_observer.OnNext(value);
}
public IDisposable Subscribe(IObserver<U> observer)
{
if (observer == null)
throw new ArgumentNullException("observer");
//
// [OK] Use of unsafe Subscribe: non-pretentious wrapping of an observable sequence.
//
return _observable.Subscribe/*Unsafe*/(observer);
}
}
class AnonymousSubject<T> : AnonymousSubject<T, T>, ISubject<T>
{
public AnonymousSubject(IObserver<T> observer, IObservable<T> observable)
: base(observer, observable)
{
}
}
}
}

19
src/Perspex.Base/PerspexProperty.cs

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Subjects;
using System.Reflection;
using Perspex.Data;
using Perspex.Utilities;
namespace Perspex
@ -323,10 +324,10 @@ namespace Perspex
/// indexer.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>A <see cref="BindingDescriptor"/> describing the binding.</returns>
public static BindingDescriptor operator !(PerspexProperty property)
/// <returns>A <see cref="IndexerDescriptor"/> describing the binding.</returns>
public static IndexerDescriptor operator !(PerspexProperty property)
{
return new BindingDescriptor
return new IndexerDescriptor
{
Priority = BindingPriority.LocalValue,
Property = property,
@ -338,10 +339,10 @@ namespace Perspex
/// indexer.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>A <see cref="BindingDescriptor"/> describing the binding.</returns>
public static BindingDescriptor operator ~(PerspexProperty property)
/// <returns>A <see cref="IndexerDescriptor"/> describing the binding.</returns>
public static IndexerDescriptor operator ~(PerspexProperty property)
{
return new BindingDescriptor
return new IndexerDescriptor
{
Priority = BindingPriority.TemplatedParent,
Property = property,
@ -557,13 +558,13 @@ namespace Perspex
/// Returns a binding accessor that can be passed to <see cref="PerspexObject"/>'s []
/// operator to initiate a binding.
/// </summary>
/// <returns>A <see cref="BindingDescriptor"/>.</returns>
/// <returns>A <see cref="IndexerDescriptor"/>.</returns>
/// <remarks>
/// The ! and ~ operators are short forms of this.
/// </remarks>
public BindingDescriptor Bind()
public IndexerDescriptor Bind()
{
return new BindingDescriptor
return new IndexerDescriptor
{
Property = this,
};

2
src/Perspex.Base/PerspexPropertyChangedEventArgs.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 Perspex.Data;
namespace Perspex
{
/// <summary>

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

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Perspex.Data;
namespace Perspex
{

1
src/Perspex.Controls/Control.cs

@ -11,6 +11,7 @@ using System.Reactive.Subjects;
using Perspex.Collections;
using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using Perspex.Data;
using Perspex.Input;
using Perspex.Interactivity;
using Perspex.LogicalTree;

31
src/Perspex.Controls/ControlExtensions.cs

@ -3,6 +3,7 @@
using System;
using System.Linq;
using Perspex.Data;
using Perspex.LogicalTree;
using Perspex.Styling;
@ -13,6 +14,36 @@ namespace Perspex.Controls
/// </summary>
public static class ControlExtensions
{
/// <summary>
/// Binds a property on an <see cref="IControl"/> to an <see cref="IBinding"/>.
/// </summary>
/// <param name="o">The object.</param>
/// <param name="property">The property to bind.</param>
/// <param name="binding">The binding.</param>
/// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns>
public static IDisposable Bind(
this IControl o,
PerspexProperty property,
IBinding binding)
{
Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(property != null);
Contract.Requires<ArgumentNullException>(binding != null);
var mode = binding.Mode;
if (mode == BindingMode.Default)
{
mode = property.DefaultBindingMode;
}
return o.Bind(
property,
binding.CreateSubject(o, property),
mode,
binding.Priority);
}
/// <summary>
/// Tries to being the control into view.
/// </summary>

61
src/Perspex.Controls/DropDown.cs

@ -16,81 +16,68 @@ namespace Perspex.Controls
/// <summary>
/// A drop-down list control.
/// </summary>
public class DropDown : SelectingItemsControl, IContentControl
public class DropDown : SelectingItemsControl
{
public static readonly PerspexProperty<object> ContentProperty =
ContentControl.ContentProperty.AddOwner<DropDown>();
public static readonly PerspexProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
ContentControl.HorizontalContentAlignmentProperty.AddOwner<DropDown>();
public static readonly PerspexProperty<VerticalAlignment> VerticalContentAlignmentProperty =
ContentControl.VerticalContentAlignmentProperty.AddOwner<DropDown>();
/// <summary>
/// Defines the <see cref="IsDropDownOpen"/> property.
/// </summary>
public static readonly PerspexProperty<bool> IsDropDownOpenProperty =
PerspexProperty.RegisterDirect<DropDown, bool>(
nameof(IsDropDownOpen),
o => o.IsDropDownOpen,
(o, v) => o.IsDropDownOpen = v);
/// <summary>
/// Defines the <see cref="SelectionBoxItem"/> property.
/// </summary>
public static readonly PerspexProperty<object> SelectionBoxItemProperty =
PerspexProperty.Register<DropDown, object>("SelectionBoxItem");
PerspexProperty.RegisterDirect<DropDown, object>("SelectionBoxItem", o => o.SelectionBoxItem);
private bool _isDropDownOpen;
private Popup _popup;
private object _selectionBoxItem;
/// <summary>
/// Initializes static members of the <see cref="DropDown"/> class.
/// </summary>
static DropDown()
{
FocusableProperty.OverrideDefaultValue<DropDown>(true);
SelectedItemProperty.Changed.AddClassHandler<DropDown>(x => x.SelectedItemChanged);
}
public DropDown()
{
Bind(ContentProperty, GetObservable(SelectedItemProperty));
}
public object Content
{
get { return GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public HorizontalAlignment HorizontalContentAlignment
{
get { return GetValue(HorizontalContentAlignmentProperty); }
set { SetValue(HorizontalContentAlignmentProperty, value); }
}
public VerticalAlignment VerticalContentAlignment
{
get { return GetValue(VerticalContentAlignmentProperty); }
set { SetValue(VerticalContentAlignmentProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating whether the dropdown is currently open.
/// </summary>
public bool IsDropDownOpen
{
get { return _isDropDownOpen; }
set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); }
}
/// <summary>
/// Gets or sets the item to display as the control's content.
/// </summary>
protected object SelectionBoxItem
{
get { return GetValue(SelectionBoxItemProperty); }
set { SetValue(SelectionBoxItemProperty, value); }
get { return _selectionBoxItem; }
set { SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); }
}
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator<DropDownItem>(this, DropDownItem.ContentProperty);
}
/// <inheritdoc/>
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
this.UpdateSelectionBoxItem(this.SelectedItem);
}
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
@ -111,6 +98,7 @@ namespace Perspex.Controls
}
}
/// <inheritdoc/>
protected override void OnPointerPressed(PointerPressEventArgs e)
{
if (!IsDropDownOpen && ((IVisual)e.Source).GetVisualRoot() != typeof(PopupRoot))
@ -131,6 +119,7 @@ namespace Perspex.Controls
base.OnPointerPressed(e);
}
/// <inheritdoc/>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
if (_popup != null)

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

@ -61,7 +61,7 @@ namespace Perspex.Controls.Presenters
{
AddHandler(RequestBringIntoViewEvent, BringIntoViewRequested);
GetObservable(ChildProperty).Subscribe(ChildChanged);
this.GetObservable(ChildProperty).Subscribe(ChildChanged);
}
/// <summary>
@ -236,7 +236,7 @@ namespace Perspex.Controls.Presenters
{
scrollable.InvalidateScroll = () => UpdateFromScrollable(scrollable);
_scrollableSubscription = new CompositeDisposable(
GetObservable(OffsetProperty).Skip(1).Subscribe(x => scrollable.Offset = x),
this.GetObservable(OffsetProperty).Skip(1).Subscribe(x => scrollable.Offset = x),
Disposable.Create(() => scrollable.InvalidateScroll = null));
UpdateFromScrollable(scrollable);
}

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

@ -33,15 +33,15 @@ namespace Perspex.Controls.Presenters
_caretTimer.Interval = TimeSpan.FromMilliseconds(500);
_caretTimer.Tick += CaretTimerTick;
_canScrollHorizontally = GetObservable(TextWrappingProperty)
_canScrollHorizontally = this.GetObservable(TextWrappingProperty)
.Select(x => x == TextWrapping.NoWrap);
Observable.Merge(
GetObservable(SelectionStartProperty),
GetObservable(SelectionEndProperty))
this.GetObservable(SelectionStartProperty),
this.GetObservable(SelectionEndProperty))
.Subscribe(_ => InvalidateFormattedText());
GetObservable(CaretIndexProperty)
this.GetObservable(CaretIndexProperty)
.Subscribe(CaretIndexChanged);
}

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

@ -37,7 +37,7 @@ namespace Perspex.Controls.Primitives
/// </summary>
public AccessText()
{
GetObservable(TextProperty).Subscribe(TextChanged);
this.GetObservable(TextProperty).Subscribe(TextChanged);
}
/// <summary>

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

@ -4,6 +4,7 @@
using System;
using System.Reactive;
using System.Reactive.Linq;
using Perspex.Data;
namespace Perspex.Controls.Primitives
{
@ -36,10 +37,10 @@ namespace Perspex.Controls.Primitives
public ScrollBar()
{
var isVisible = Observable.Merge(
GetObservable(MinimumProperty).Select(_ => Unit.Default),
GetObservable(MaximumProperty).Select(_ => Unit.Default),
GetObservable(ViewportSizeProperty).Select(_ => Unit.Default),
GetObservable(VisibilityProperty).Select(_ => Unit.Default))
this.GetObservable(MinimumProperty).Select(_ => Unit.Default),
this.GetObservable(MaximumProperty).Select(_ => Unit.Default),
this.GetObservable(ViewportSizeProperty).Select(_ => Unit.Default),
this.GetObservable(VisibilityProperty).Select(_ => Unit.Default))
.Select(_ => CalculateIsVisible());
Bind(IsVisibleProperty, isVisible, BindingPriority.Style);
}

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

@ -8,6 +8,7 @@ using System.Collections.Specialized;
using System.Linq;
using Perspex.Collections;
using Perspex.Controls.Generators;
using Perspex.Data;
using Perspex.Input;
using Perspex.Interactivity;
using Perspex.Styling;

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

@ -6,6 +6,7 @@ using System.Linq;
using System.Reactive.Linq;
using Perspex.Controls.Presenters;
using Perspex.Controls.Templates;
using Perspex.Data;
using Perspex.Interactivity;
using Perspex.Media;
using Perspex.Styling;
@ -224,14 +225,14 @@ namespace Perspex.Controls.Primitives
}
}
protected sealed override BindingDescriptor CreateBindingDescriptor(BindingDescriptor source)
protected sealed override IndexerDescriptor CreateBindingDescriptor(IndexerDescriptor source)
{
var result = base.CreateBindingDescriptor(source);
// If the binding is a template binding, then complete when the Template changes.
if (source.Priority == BindingPriority.TemplatedParent)
{
var templateChanged = GetObservable(TemplateProperty).Skip(1);
var templateChanged = this.GetObservable(TemplateProperty).Skip(1);
result.SourceObservable = result.Source.GetObservable(result.Property)
.TakeUntil(templateChanged);

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

@ -38,7 +38,7 @@ namespace Perspex.Controls.Primitives
public Track()
{
GetObservableWithHistory(ThumbProperty).Subscribe(val =>
this.GetObservableWithHistory(ThumbProperty).Subscribe(val =>
{
if (val.Item1 != null)
{

2
src/Perspex.Controls/RadioButton.cs

@ -12,7 +12,7 @@ namespace Perspex.Controls
{
public RadioButton()
{
GetObservable(IsCheckedProperty).Subscribe(IsCheckedChanged);
this.GetObservable(IsCheckedProperty).Subscribe(IsCheckedChanged);
}
protected override void Toggle()

10
src/Perspex.Controls/ScrollViewer.cs

@ -135,8 +135,8 @@ namespace Perspex.Controls
public ScrollViewer()
{
var extentAndViewport = Observable.CombineLatest(
GetObservable(ExtentProperty),
GetObservable(ViewportProperty))
this.GetObservable(ExtentProperty),
this.GetObservable(ViewportProperty))
.Select(x => new { Extent = x[0], Viewport = x[1] });
Bind(
@ -155,15 +155,15 @@ namespace Perspex.Controls
VerticalScrollBarMaximumProperty,
extentAndViewport.Select(x => Max(x.Extent.Height - x.Viewport.Height, 0)));
GetObservable(OffsetProperty).Subscribe(x =>
this.GetObservable(OffsetProperty).Subscribe(x =>
{
SetValue(HorizontalScrollBarValueProperty, x.X);
SetValue(VerticalScrollBarValueProperty, x.Y);
});
var scrollBarOffset = Observable.CombineLatest(
GetObservable(HorizontalScrollBarValueProperty),
GetObservable(VerticalScrollBarValueProperty))
this.GetObservable(HorizontalScrollBarValueProperty),
this.GetObservable(VerticalScrollBarValueProperty))
.Select(x => new Vector(x[0], x[1]))
.Subscribe(x => Offset = x);
}

9
src/Perspex.Controls/TextBlock.cs

@ -4,6 +4,7 @@
using System;
using System.Reactive;
using System.Reactive.Linq;
using Perspex.Data;
using Perspex.Media;
using Perspex.Metadata;
@ -106,10 +107,10 @@ namespace Perspex.Controls
public TextBlock()
{
Observable.Merge(
GetObservable(TextProperty).Select(_ => Unit.Default),
GetObservable(TextAlignmentProperty).Select(_ => Unit.Default),
GetObservable(FontSizeProperty).Select(_ => Unit.Default),
GetObservable(FontStyleProperty).Select(_ => Unit.Default))
this.GetObservable(TextProperty).Select(_ => Unit.Default),
this.GetObservable(TextAlignmentProperty).Select(_ => Unit.Default),
this.GetObservable(FontSizeProperty).Select(_ => Unit.Default),
this.GetObservable(FontStyleProperty).Select(_ => Unit.Default))
.Subscribe(_ =>
{
InvalidateFormattedText();

5
src/Perspex.Controls/TextBox.cs

@ -14,6 +14,7 @@ using Perspex.Input;
using Perspex.Interactivity;
using Perspex.Media;
using Perspex.Metadata;
using Perspex.Data;
namespace Perspex.Controls
{
@ -73,7 +74,7 @@ namespace Perspex.Controls
public TextBox()
{
var canScrollHorizontally = GetObservable(AcceptsReturnProperty)
var canScrollHorizontally = this.GetObservable(AcceptsReturnProperty)
.Select(x => !x);
Bind(
@ -81,7 +82,7 @@ namespace Perspex.Controls
canScrollHorizontally,
BindingPriority.Style);
var horizontalScrollBarVisibility = GetObservable(AcceptsReturnProperty)
var horizontalScrollBarVisibility = this.GetObservable(AcceptsReturnProperty)
.Select(x => x ? ScrollBarVisibility.Auto : ScrollBarVisibility.Hidden);
Bind(

4
src/Perspex.Controls/TopLevel.cs

@ -115,8 +115,8 @@ namespace Perspex.Controls
_accessKeyHandler?.SetOwner(this);
styler?.ApplyStyles(this);
GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl.ClientSize = x);
GetObservable(PointerOverElementProperty)
this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl.ClientSize = x);
this.GetObservable(PointerOverElementProperty)
.Select(
x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty<Cursor>())
.Switch().Subscribe(cursor => PlatformImpl.SetCursor(cursor?.PlatformCursor));

1
src/Perspex.Diagnostics/Debug.cs

@ -7,6 +7,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Perspex.Controls;
using Perspex.Data;
namespace Perspex.Diagnostics
{

2
src/Perspex.Diagnostics/DevTools.cs

@ -21,7 +21,7 @@ namespace Perspex.Diagnostics
public DevTools()
{
_viewModel = new DevToolsViewModel();
GetObservable(RootProperty).Subscribe(x => _viewModel.Root = x);
this.GetObservable(RootProperty).Subscribe(x => _viewModel.Root = x);
InitializeComponent();
}

1
src/Perspex.Diagnostics/ViewModels/PropertyDetails.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Perspex.Data;
using ReactiveUI;
namespace Perspex.Diagnostics.ViewModels

2
src/Perspex.Diagnostics/Views/ControlDetailsView.cs

@ -19,7 +19,7 @@ namespace Perspex.Diagnostics.Views
public ControlDetailsView()
{
InitializeComponent();
GetObservable(DataContextProperty)
this.GetObservable(DataContextProperty)
.Subscribe(x => ViewModel = (ControlDetailsViewModel)x);
}

2
src/Perspex.Diagnostics/Views/LogicalTreeView.cs

@ -20,7 +20,7 @@ namespace Perspex.Diagnostics.Views
public LogicalTreeView()
{
InitializeComponent();
GetObservable(DataContextProperty)
this.GetObservable(DataContextProperty)
.Subscribe(x => ViewModel = (LogicalTreeViewModel)x);
}

2
src/Perspex.Diagnostics/Views/VisualTreeView.cs

@ -21,7 +21,7 @@ namespace Perspex.Diagnostics.Views
public VisualTreeView()
{
InitializeComponent();
GetObservable(DataContextProperty)
this.GetObservable(DataContextProperty)
.Subscribe(x => ViewModel = (VisualTreeViewModel)x);
}

4
src/Perspex.SceneGraph/Animation/CrossFade.cs

@ -58,7 +58,7 @@ namespace Perspex.Animation
if (from != null)
{
tasks.Add(Animate.Property(
(IObservablePropertyBag)from,
(IPerspexObject)from,
Visual.OpacityProperty,
from.Opacity,
0,
@ -72,7 +72,7 @@ namespace Perspex.Animation
to.IsVisible = true;
tasks.Add(Animate.Property(
(IObservablePropertyBag)to,
(IPerspexObject)to,
Visual.OpacityProperty,
0,
1,

2
src/Perspex.SceneGraph/Media/MatrixTransform.cs

@ -21,7 +21,7 @@ namespace Perspex.Media
/// </summary>
public MatrixTransform()
{
GetObservable(MatrixProperty).Subscribe(_ => RaiseChanged());
this.GetObservable(MatrixProperty).Subscribe(_ => RaiseChanged());
}
/// <summary>

2
src/Perspex.SceneGraph/Media/RotateTransform.cs

@ -21,7 +21,7 @@ namespace Perspex.Media
/// </summary>
public RotateTransform()
{
GetObservable(AngleProperty).Subscribe(_ => RaiseChanged());
this.GetObservable(AngleProperty).Subscribe(_ => RaiseChanged());
}
/// <summary>

4
src/Perspex.SceneGraph/Media/TranslateTransform.cs

@ -27,8 +27,8 @@ namespace Perspex.Media
/// </summary>
public TranslateTransform()
{
GetObservable(XProperty).Subscribe(_ => RaiseChanged());
GetObservable(YProperty).Subscribe(_ => RaiseChanged());
this.GetObservable(XProperty).Subscribe(_ => RaiseChanged());
this.GetObservable(YProperty).Subscribe(_ => RaiseChanged());
}
/// <summary>

1
src/Perspex.SceneGraph/Visual.cs

@ -8,6 +8,7 @@ using System.Linq;
using System.Reactive.Linq;
using Perspex.Animation;
using Perspex.Collections;
using Perspex.Data;
using Perspex.Media;
using Perspex.Platform;
using Perspex.Rendering;

1
src/Perspex.Styling/Perspex.Styling.csproj

@ -56,7 +56,6 @@
<Compile Include="Styling\SelectorMatch.cs" />
<Compile Include="Styling\Selector.cs" />
<Compile Include="Styling\Selectors.cs" />
<Compile Include="Styling\ObservableSetter.cs" />
<Compile Include="Styling\Setter.cs" />
<Compile Include="Styling\Style.cs" />
<Compile Include="Styling\StyleActivator.cs" />

2
src/Perspex.Styling/Styling/IStyleable.cs

@ -10,7 +10,7 @@ namespace Perspex.Styling
/// <summary>
/// Interface for styleable elements.
/// </summary>
public interface IStyleable : IObservablePropertyBag, INamed
public interface IStyleable : IPerspexObject, INamed
{
/// <summary>
/// Raised when the control's style should be removed.

9
src/Perspex.Styling/Styling/ITemplatedControl.cs

@ -5,14 +5,7 @@ using System;
namespace Perspex.Styling
{
public interface ITemplatedControl
public interface ITemplatedControl : IPerspexObject
{
/// <summary>
/// Gets an observable for a <see cref="PerspexProperty"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="property">The property to get the observable for.</param>
/// <returns>The observable.</returns>
IObservable<T> GetObservable<T>(PerspexProperty<T> property);
}
}

65
src/Perspex.Styling/Styling/ObservableSetter.cs

@ -1,65 +0,0 @@
// 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.Styling
{
/// <summary>
/// A setter for a <see cref="Style"/> whose source is an observable.
/// </summary>
/// <remarks>
/// A <see cref="Setter"/> is used to set a <see cref="PerspexProperty"/> value on a
/// <see cref="PerspexObject"/> depending on a condition.
/// </remarks>
public class ObservableSetter : ISetter
{
/// <summary>
/// Initializes a new instance of the <see cref="ObservableSetter"/> class.
/// </summary>
/// <param name="property">The property to set.</param>
/// <param name="source">An observable which produces the value for the property.</param>
public ObservableSetter(PerspexProperty property, IObservable<object> source)
{
Property = property;
Source = source;
}
/// <summary>
/// Gets or sets the property to set.
/// </summary>
public PerspexProperty Property
{
get;
set;
}
/// <summary>
/// Gets or sets an observable which produces the value for the property.
/// </summary>
public IObservable<object> Source
{
get;
set;
}
/// <summary>
/// Applies the setter to the control.
/// </summary>
/// <param name="style">The style that is being applied.</param>
/// <param name="control">The control.</param>
/// <param name="activator">An optional activator.</param>
public void Apply(IStyle style, IStyleable control, IObservable<bool> activator)
{
if (activator == null)
{
control.Bind(Property, Source, BindingPriority.Style);
}
else
{
var binding = new StyleBinding(activator, Source, style.ToString());
control.Bind(Property, binding, BindingPriority.StyleTrigger);
}
}
}
}

2
src/Perspex.Styling/Styling/Selectors.cs

@ -240,7 +240,7 @@ namespace Perspex.Styling
private static SelectorMatch MatchPropertyEquals(IStyleable x, PerspexProperty property, object value)
{
if (!x.IsRegistered(property))
if (!PerspexPropertyRegistry.Instance.IsRegistered(x, property))
{
return SelectorMatch.False;
}

48
src/Perspex.Styling/Styling/Setter.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Perspex.Data;
using Perspex.Metadata;
namespace Perspex.Styling
@ -46,6 +47,7 @@ namespace Perspex.Styling
/// Gets or sets the property value.
/// </summary>
[Content]
[AssignBinding]
public object Value
{
get;
@ -60,15 +62,53 @@ namespace Perspex.Styling
/// <param name="activator">An optional activator.</param>
public void Apply(IStyle style, IStyleable control, IObservable<bool> activator)
{
if (activator == null)
if (Property == null)
{
control.SetValue(Property, Value, BindingPriority.Style);
throw new InvalidOperationException("Setter.Property must be set.");
}
var binding = Value as IBinding;
if (binding != null)
{
if (activator == null)
{
Bind(control, Property, binding);
}
else
{
throw new NotSupportedException(
"Setter bindings with activators not yet supported.");
}
}
else
{
var binding = new StyleBinding(activator, Value, style.ToString());
control.Bind(Property, binding, BindingPriority.StyleTrigger);
if (activator == null)
{
control.SetValue(Property, Value, BindingPriority.Style);
}
else
{
var activated = new StyleBinding(activator, Value, style.ToString());
control.Bind(Property, activated, BindingPriority.StyleTrigger);
}
}
}
private void Bind(IStyleable control, PerspexProperty property, IBinding binding)
{
var mode = binding.Mode;
if (mode == BindingMode.Default)
{
mode = property.DefaultBindingMode;
}
control.Bind(
property,
binding.CreateSubject(control, property),
mode,
binding.Priority);
}
}
}

6
src/Perspex.Themes.Default/DropDown.paml

@ -2,8 +2,6 @@
<Style Selector="DropDown">
<Setter Property="BorderBrush" Value="#ff707070"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="4"/>
<Setter Property="Template">
<ControlTemplate>
@ -13,8 +11,8 @@
<Grid ColumnDefinitions="*,Auto">
<ContentPresenter Content="{TemplateBinding SelectionBoxItem}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<ToggleButton Name="toggle"
BorderThickness="0"
Background="Transparent"

2
src/Perspex.Themes.Default/DropDownItem.paml

@ -15,7 +15,7 @@
</Setter>
</Style>
<Style Selector="DropDownItem:selected /template/ Border#border">
<Setter Property="Background" Value="#ffe5f0fc"/>
<Setter Property="Background" Value="#fff0f0f0"/>
</Style>
<Style Selector="DropDownItem:pointerover /template/ Border#border">
<Setter Property="Background" Value="#ffd0d0d0"/>

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

@ -82,6 +82,7 @@
<Compile Include="Collections\PerspexListTests.cs" />
<Compile Include="Collections\PropertyChangedTracker.cs" />
<Compile Include="PerspexObjectTests_Direct.cs" />
<Compile Include="PerspexObjectTests_GetSubject.cs" />
<Compile Include="PerspexObjectTests_GetObservable.cs" />
<Compile Include="PerspexPropertyRegistryTests.cs" />
<Compile Include="PerspexObjectTests_Validation.cs" />

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

@ -4,6 +4,7 @@
using System;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Perspex.Data;
using Xunit;
namespace Perspex.Base.UnitTests
@ -234,7 +235,7 @@ namespace Perspex.Base.UnitTests
{
Class1 target1 = new Class1();
Class1 target2 = new Class1();
BindingDescriptor binding = Class1.FooProperty.Bind().WithMode(BindingMode.OneWay);
IndexerDescriptor binding = Class1.FooProperty.Bind().WithMode(BindingMode.OneWay);
target1.SetValue(Class1.FooProperty, "first");
target2[binding] = target1[!Class1.FooProperty];
@ -248,7 +249,7 @@ namespace Perspex.Base.UnitTests
{
Class1 target1 = new Class1();
Class1 target2 = new Class1();
BindingDescriptor binding = Class1.FooProperty.Bind().WithMode(BindingMode.TwoWay);
IndexerDescriptor binding = Class1.FooProperty.Bind().WithMode(BindingMode.TwoWay);
target1.SetValue(Class1.FooProperty, "first");
target2[binding] = target1[!Class1.FooProperty];
@ -264,7 +265,7 @@ namespace Perspex.Base.UnitTests
{
Class1 target1 = new Class1();
Class1 target2 = new Class1();
BindingDescriptor binding = Class1.FooProperty.Bind().WithMode(BindingMode.OneTime);
IndexerDescriptor binding = Class1.FooProperty.Bind().WithMode(BindingMode.OneTime);
target1.SetValue(Class1.FooProperty, "first");
target2[binding] = target1[!Class1.FooProperty];

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

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Reactive.Subjects;
using Perspex.Data;
using Xunit;
namespace Perspex.Base.UnitTests

49
tests/Perspex.Base.UnitTests/PerspexObjectTests_GetSubject.cs

@ -0,0 +1,49 @@
// 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.Linq;
using Xunit;
namespace Perspex.Base.UnitTests
{
public class PerspexObjectTests_GetSubject
{
[Fact]
public void GetSubject_Returns_Values()
{
var source = new Class1 { Foo = "foo" };
var target = source.GetSubject(Class1.FooProperty);
var result = new List<string>();
target.Subscribe(x => result.Add(x));
source.Foo = "bar";
source.Foo = "baz";
Assert.Equal(new[] { "foo", "bar", "baz" }, result);
}
[Fact]
public void GetSubject_Sets_Values()
{
var source = new Class1 { Foo = "foo" };
var target = source.GetSubject(Class1.FooProperty);
target.OnNext("bar");
Assert.Equal("bar", source.Foo);
}
private class Class1 : PerspexObject
{
public static readonly PerspexProperty<string> FooProperty =
PerspexProperty.Register<Class1, string>("Foo", "foodefault");
public string Foo
{
get { return GetValue(FooProperty); }
set { SetValue(FooProperty, value); }
}
}
}
}

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

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Perspex.Data;
using Xunit;
namespace Perspex.Base.UnitTests

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

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Perspex.Data;
using Xunit;
namespace Perspex.Base.UnitTests

2
tests/Perspex.Controls.UnitTests/DropDownTests.cs

@ -40,7 +40,7 @@ namespace Perspex.Controls.UnitTests
new ContentControl
{
Name = "contentControl",
[~ContentPresenter.ContentProperty] = parent[~DropDown.ContentProperty],
[~ContentPresenter.ContentProperty] = parent[~DropDown.SelectionBoxItemProperty],
},
new ToggleButton
{

7
tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs

@ -9,6 +9,7 @@ using Perspex.Collections;
using Perspex.Controls.Presenters;
using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using Perspex.Data;
using Perspex.Markup.Xaml.Data;
using Xunit;
@ -418,8 +419,8 @@ namespace Perspex.Controls.UnitTests.Primitives
};
// Bind Items and SelectedItems to the VM.
itemsBinding.Bind(target, TestSelector.ItemsProperty);
selectedItemsBinding.Bind(target, TestSelector.SelectedItemsProperty);
target.Bind(TestSelector.ItemsProperty, itemsBinding);
target.Bind(TestSelector.SelectedItemsProperty, selectedItemsBinding);
// Set DataContext and SelectedIndex
target.DataContext = vm;
@ -451,7 +452,7 @@ namespace Perspex.Controls.UnitTests.Primitives
};
var itemsBinding = new Binding { Path = "Items" };
itemsBinding.Bind(target, TestSelector.ItemsProperty);
target.Bind(TestSelector.ItemsProperty, itemsBinding);
Assert.Same(data.Items, target.Items);

3
tests/Perspex.LeakTests/ControlTests.cs

@ -194,7 +194,8 @@ namespace Perspex.LeakTests
Path = "Name"
};
binding.Bind((TextBox)window.Content, TextBox.TextProperty);
var textBox = (TextBox)window.Content;
textBox.Bind(TextBox.TextProperty, binding);
// Do a layout and make sure that TextBox gets added to visual tree and its
// Text property set.

134
tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs

@ -1,14 +1,12 @@
// 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.Globalization;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Moq;
using Perspex.Controls;
using Perspex.Data;
using Perspex.Markup.Data;
using Perspex.Markup.Xaml.Data;
using ReactiveUI;
using Xunit;
namespace Perspex.Markup.Xaml.UnitTests.Data
@ -18,101 +16,101 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
[Fact]
public void OneWay_Binding_Should_Be_Set_Up()
{
var target = CreateTarget();
var source = new Source { Foo = "foo" };
var target = new TextBlock { DataContext = source };
var binding = new Binding
{
Path = "Foo",
Mode = BindingMode.OneWay,
};
binding.Bind(target.Object, TextBox.TextProperty);
target.Bind(TextBox.TextProperty, binding);
target.Verify(x => x.Bind(
TextBox.TextProperty,
It.IsAny<IObservable<object>>(),
BindingPriority.LocalValue));
Assert.Equal("foo", target.Text);
source.Foo = "bar";
Assert.Equal("bar", target.Text);
target.Text = "baz";
Assert.Equal("bar", source.Foo);
}
[Fact]
public void TwoWay_Binding_Should_Be_Set_Up()
{
var target = CreateTarget();
var source = new Source { Foo = "foo" };
var target = new TextBlock { DataContext = source };
var binding = new Binding
{
Path = "Foo",
Mode = BindingMode.TwoWay,
};
binding.Bind(target.Object, TextBox.TextProperty);
target.Bind(TextBox.TextProperty, binding);
target.Verify(x => x.BindTwoWay(
TextBox.TextProperty,
It.IsAny<ISubject<object>>(),
BindingPriority.LocalValue));
Assert.Equal("foo", target.Text);
source.Foo = "bar";
Assert.Equal("bar", target.Text);
target.Text = "baz";
Assert.Equal("baz", source.Foo);
}
[Fact]
public void OneTime_Binding_Should_Be_Set_Up()
{
var dataContext = new BehaviorSubject<object>(null);
var expression = new BehaviorSubject<object>(null);
var target = CreateTarget(dataContext: dataContext);
var source = new Source { Foo = "foo" };
var target = new TextBlock { DataContext = source };
var binding = new Binding
{
Path = "Foo",
Mode = BindingMode.OneTime,
};
binding.Bind(target.Object, TextBox.TextProperty, expression);
target.Bind(TextBox.TextProperty, binding);
target.Verify(x => x.SetValue(
(PerspexProperty)TextBox.TextProperty,
null,
BindingPriority.LocalValue));
target.ResetCalls();
expression.OnNext("foo");
dataContext.OnNext(1);
target.Verify(x => x.SetValue(
(PerspexProperty)TextBox.TextProperty,
"foo",
BindingPriority.LocalValue));
Assert.Equal("foo", target.Text);
source.Foo = "bar";
Assert.Equal("foo", target.Text);
target.Text = "baz";
Assert.Equal("bar", source.Foo);
}
[Fact]
public void OneWayToSource_Binding_Should_Be_Set_Up()
{
var textObservable = new Mock<IObservable<string>>();
var expression = new Mock<ISubject<object>>();
var target = CreateTarget(text: textObservable.Object);
var source = new Source { Foo = "foo" };
var target = new TextBlock { DataContext = source, Text = "bar" };
var binding = new Binding
{
Path = "Foo",
Mode = BindingMode.OneWayToSource,
};
binding.Bind(target.Object, TextBox.TextProperty, expression.Object);
target.Bind(TextBox.TextProperty, binding);
textObservable.Verify(x => x.Subscribe(expression.Object));
Assert.Equal("bar", source.Foo);
target.Text = "baz";
Assert.Equal("baz", source.Foo);
source.Foo = "quz";
Assert.Equal("baz", target.Text);
}
[Fact]
public void Default_BindingMode_Should_Be_Used()
{
var target = CreateTarget(null);
// Default for TextBox.Text is two-way.
var source = new Source { Foo = "foo" };
var target = new TextBlock { DataContext = source };
var binding = new Binding
{
Path = "Foo",
};
binding.Bind(target.Object, TextBox.TextProperty);
target.Bind(TextBox.TextProperty, binding);
// Default for TextBox.Text is two-way.
target.Verify(x => x.BindTwoWay(
TextBox.TextProperty,
It.IsAny<ISubject<object>>(),
BindingPriority.LocalValue));
Assert.Equal("foo", target.Text);
source.Foo = "bar";
Assert.Equal("bar", target.Text);
target.Text = "baz";
Assert.Equal("baz", source.Foo);
}
[Fact]
@ -131,7 +129,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Path = "Header",
};
binding.Bind(parent.Child, Control.DataContextProperty);
parent.Child.Bind(Control.DataContextProperty, binding);
Assert.Equal("Foo", parent.Child.DataContext);
@ -155,7 +153,8 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Path = "Foo",
};
binding.Bind(child, Control.DataContextProperty);
child.Bind(Control.DataContextProperty, binding);
Assert.Null(child.DataContext);
parent.Child = child;
Assert.Equal("foo", child.DataContext);
@ -164,13 +163,13 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
[Fact]
public void Should_Use_DefaultValueConverter_When_No_Converter_Specified()
{
var target = CreateTarget(null);
var target = new TextBlock(); ;
var binding = new Binding
{
Path = "Foo",
};
var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType);
var result = binding.CreateSubject(target, TextBox.TextProperty);
Assert.IsType<DefaultValueConverter>(((ExpressionSubject)result).Converter);
}
@ -178,7 +177,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
[Fact]
public void Should_Use_Supplied_Converter()
{
var target = CreateTarget(null);
var target = new TextBlock();
var converter = new Mock<IValueConverter>();
var binding = new Binding
{
@ -186,7 +185,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Path = "Foo",
};
var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType);
var result = binding.CreateSubject(target, TextBox.TextProperty);
Assert.Same(converter.Object, ((ExpressionSubject)result).Converter);
}
@ -194,7 +193,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
[Fact]
public void Should_Pass_ConverterParameter_To_Supplied_Converter()
{
var target = CreateTarget();
var target = new TextBlock();
var converter = new Mock<IValueConverter>();
var binding = new Binding
{
@ -203,7 +202,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Path = "Bar",
};
var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType);
var result = binding.CreateSubject(target, TextBox.TextProperty);
Assert.Same("foo", ((ExpressionSubject)result).ConverterParameter);
}
@ -239,8 +238,8 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
};
// Bind Foo and Bar to the VM.
fooBinding.Bind(target, OldDataContextTest.FooProperty);
barBinding.Bind(target, OldDataContextTest.BarProperty);
target.Bind(OldDataContextTest.FooProperty, fooBinding);
target.Bind(OldDataContextTest.BarProperty, barBinding);
target.DataContext = vm;
// Make sure the control's Foo and Bar properties are read from the VM
@ -261,24 +260,15 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Assert.Equal(2, vm.Bar);
}
private Mock<IObservablePropertyBag> CreateTarget(object dataContext)
public class Source : ReactiveObject
{
return CreateTarget(dataContext: Observable.Never<object>().StartWith(dataContext));
}
private string _foo;
private Mock<IObservablePropertyBag> CreateTarget(
IObservable<object> dataContext = null,
IObservable<string> text = null)
{
var result = new Mock<IObservablePropertyBag>();
dataContext = dataContext ?? Observable.Never<object>().StartWith((object)null);
text = text ?? Observable.Never<string>().StartWith((string)null);
result.Setup(x => x.GetObservable(Control.DataContextProperty)).Returns(dataContext);
result.Setup(x => x.GetObservable((PerspexProperty)Control.DataContextProperty)).Returns(dataContext);
result.Setup(x => x.GetObservable((PerspexProperty)TextBox.TextProperty)).Returns(text);
return result;
public string Foo
{
get { return _foo; }
set { this.RaiseAndSetIfChanged(ref _foo, value); }
}
}
private class OldDataContextViewModel
@ -297,7 +287,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
public OldDataContextTest()
{
Bind(BarProperty, GetObservable(FooProperty));
Bind(BarProperty, this.GetObservable(FooProperty));
}
}
}

8
tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs

@ -38,7 +38,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Path = "Text",
};
binding.Bind(target, TextBlock.TextProperty);
target.Bind(TextBox.TextProperty, binding);
Assert.Equal("foo", target.Text);
}
@ -73,7 +73,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
ElementName = "source",
};
binding.Bind(target, ContentControl.ContentProperty);
target.Bind(ContentControl.ContentProperty, binding);
Assert.Same(source, target.Content);
}
@ -104,7 +104,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Path = "Text",
};
binding.Bind(target, TextBlock.TextProperty);
target.Bind(TextBox.TextProperty, binding);
stackPanel.Children.Add(new TextBlock
{
@ -140,7 +140,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
ElementName = "source",
};
binding.Bind(target, ContentControl.ContentProperty);
target.Bind(ContentControl.ContentProperty, binding);
var source = new TextBlock
{

45
tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs

@ -6,6 +6,7 @@ using System.Reactive.Linq;
using System.Reactive.Subjects;
using Moq;
using Perspex.Controls;
using Perspex.Data;
using Perspex.Markup.Xaml.Data;
using Perspex.Styling;
using Xunit;
@ -26,7 +27,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Path = "Foo",
};
binding.Bind(target.Object, TextBox.TextProperty);
target.Object.Bind(TextBox.TextProperty, binding);
target.Verify(x => x.Bind(
TextBox.TextProperty,
@ -46,48 +47,28 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Path = "Foo",
};
binding.Bind(target.Object, TextBox.TextProperty);
target.Object.Bind(TextBox.TextProperty, binding);
target.Verify(x => x.BindTwoWay(
target.Verify(x => x.Bind(
TextBox.TextProperty,
It.IsAny<ISubject<object>>(),
BindingPriority.TemplatedParent));
}
[Fact]
public void OneWayToSource_Binding_Should_Be_Set_Up()
{
var textObservable = new Mock<IObservable<string>>();
var expression = new Mock<ISubject<object>>();
var target = CreateTarget(text: textObservable.Object);
var binding = new Binding
{
Path = "Foo",
Mode = BindingMode.OneWayToSource,
};
binding.Bind(target.Object, TextBox.TextProperty, expression.Object);
textObservable.Verify(x => x.Subscribe(expression.Object));
}
private Mock<IObservablePropertyBag> CreateTarget(ITemplatedControl templatedParent)
private Mock<IPerspexObject> CreateTarget(ITemplatedControl templatedParent)
{
return CreateTarget(templatedParent: Observable.Never<ITemplatedControl>().StartWith(templatedParent));
return CreateTarget(templatedParent: templatedParent);
}
private Mock<IObservablePropertyBag> CreateTarget(
IObservable<ITemplatedControl> templatedParent = null,
IObservable<string> text = null)
private Mock<IControl> CreateTarget(
ITemplatedControl templatedParent = null,
string text = null)
{
var result = new Mock<IObservablePropertyBag>();
templatedParent = templatedParent ?? Observable.Never<ITemplatedControl>().StartWith((ITemplatedControl)null);
text = text ?? Observable.Never<string>().StartWith((string)null);
var result = new Mock<IControl>();
result.Setup(x => x.GetObservable(Control.TemplatedParentProperty)).Returns(templatedParent);
result.Setup(x => x.GetObservable((PerspexProperty)Control.TemplatedParentProperty)).Returns(templatedParent);
result.Setup(x => x.GetObservable((PerspexProperty)TextBox.TextProperty)).Returns(text);
result.Setup(x => x.GetValue(Control.TemplatedParentProperty)).Returns(templatedParent);
result.Setup(x => x.GetValue((PerspexProperty)Control.TemplatedParentProperty)).Returns(templatedParent);
result.Setup(x => x.GetValue((PerspexProperty)TextBox.TextProperty)).Returns(text);
return result;
}
}

7
tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs

@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Moq;
using Perspex.Controls;
using Perspex.Markup.Xaml.Data;
@ -31,12 +30,10 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
}
};
var target = new Mock<IObservablePropertyBag>();
var target = new Mock<IPerspexObject>();
target.Setup(x => x.GetValue(Control.DataContextProperty)).Returns(source);
target.Setup(x => x.GetObservable(Control.DataContextProperty)).Returns(
Observable.Never<object>().StartWith(source));
var subject = binding.CreateSubject(target.Object, typeof(string));
var subject = binding.CreateSubject(target.Object, null);
var result = await subject.Take(1);
Assert.Equal("1,2,3", result);

5
tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj

@ -43,9 +43,6 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Octokit">
<HintPath>..\..\packages\Octokit.0.14.0\lib\net45\Octokit.dll</HintPath>
</Reference>
<Reference Include="Splat, Version=1.6.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Splat.1.6.2\lib\Net45\Splat.dll</HintPath>
<Private>True</Private>
@ -104,7 +101,9 @@
<Compile Include="SampleModel\Repository.cs" />
<Compile Include="SampleModel\UserRepositoriesViewModel.cs" />
<Compile Include="SamplePerspexObject.cs" />
<Compile Include="StyleTests.cs" />
<Compile Include="Templates\DataTemplateTests.cs" />
<Compile Include="Templates\TreeDataTemplateTests.cs" />
<Compile Include="TestRoot.cs" />
<Compile Include="TypeProviderMock.cs" />
<Compile Include="ViewModelMock.cs" />

33
tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs

@ -0,0 +1,33 @@
// 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.Linq;
using Moq;
using Perspex.Markup.Xaml.Data;
using Perspex.Platform;
using Perspex.Styling;
using Xunit;
namespace Perspex.Markup.Xaml.UnitTests
{
public class StyleTests
{
[Fact]
public void Binding_Should_Be_Assigned_To_Setter_Value_Instead_Of_Bound()
{
using (PerspexLocator.EnterScope())
{
PerspexLocator.CurrentMutable
.Bind<IPclPlatformWrapper>()
.ToConstant(Mock.Of<IPclPlatformWrapper>());
var xaml = "<Style xmlns='https://github.com/perspex'><Setter Value='{Binding}'/></Style>";
var loader = new PerspexXamlLoader();
var style = (Style)loader.Load(xaml);
var setter = (Setter)(style.Setters.First());
Assert.IsType<Binding>(setter.Value);
}
}
}
}

34
tests/Perspex.Markup.Xaml.UnitTests/Templates/TreeDataTemplateTests.cs

@ -0,0 +1,34 @@
// 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.Linq;
using Moq;
using Perspex.Controls.Templates;
using Perspex.Markup.Xaml.Data;
using Perspex.Markup.Xaml.Templates;
using Perspex.Platform;
using Xunit;
namespace Perspex.Markup.Xaml.UnitTests
{
public class TreeDataTemplateTests
{
[Fact]
public void Binding_Should_Be_Assigned_To_ItemsSource_Instead_Of_Bound()
{
using (PerspexLocator.EnterScope())
{
PerspexLocator.CurrentMutable
.Bind<IPclPlatformWrapper>()
.ToConstant(Mock.Of<IPclPlatformWrapper>());
var xaml = "<DataTemplates xmlns='https://github.com/perspex'><TreeDataTemplate ItemsSource='{Binding}'/></DataTemplates>";
var loader = new PerspexXamlLoader();
var templates = (DataTemplates)loader.Load(xaml);
var template = (TreeDataTemplate)(templates.First());
Assert.IsType<Binding>(template.ItemsSource);
}
}
}
}

1
tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj

@ -91,6 +91,7 @@
<Compile Include="SelectorTests_Template.cs" />
<Compile Include="StyleActivatorTests.cs" />
<Compile Include="StyleBindingTests.cs" />
<Compile Include="SetterTests.cs" />
<Compile Include="StyleTests.cs" />
<Compile Include="TestControlBase.cs" />
<Compile Include="TestObservable.cs" />

54
tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs

@ -6,11 +6,9 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using Perspex.Collections;
using Perspex.Controls;
using Perspex.Styling;
using Perspex.Data;
using Xunit;
namespace Perspex.Styling.UnitTests
@ -80,6 +78,8 @@ namespace Perspex.Styling.UnitTests
Classes = new Classes();
}
public event EventHandler<PerspexPropertyChangedEventArgs> PropertyChanged;
public Classes Classes { get; }
public string Name { get; set; }
@ -96,42 +96,29 @@ namespace Perspex.Styling.UnitTests
IObservable<Unit> IStyleable.StyleDetach { get; }
public IPropertyBag InheritanceParent
{
get
{
throw new NotImplementedException();
}
}
IPerspexReadOnlyList<string> IStyleable.Classes => Classes;
public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority)
{
throw new NotImplementedException();
}
public void SetValue(PerspexProperty property, object value, BindingPriority priority)
public object GetValue(PerspexProperty property)
{
throw new NotImplementedException();
}
public IObservable<object> GetObservable(PerspexProperty property)
public T GetValue<T>(PerspexProperty<T> property)
{
throw new NotImplementedException();
}
public bool IsRegistered(PerspexProperty property)
public void SetValue(PerspexProperty property, object value, BindingPriority priority)
{
throw new NotImplementedException();
}
public void ClearValue(PerspexProperty property)
public void SetValue<T>(PerspexProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public object GetValue(PerspexProperty property)
public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority)
{
throw new NotImplementedException();
}
@ -145,31 +132,6 @@ namespace Perspex.Styling.UnitTests
{
throw new NotImplementedException();
}
public IObservable<T> GetObservable<T>(PerspexProperty<T> property)
{
throw new NotImplementedException();
}
public T GetValue<T>(PerspexProperty<T> property)
{
throw new NotImplementedException();
}
public void SetValue<T>(PerspexProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public IDisposable BindTwoWay(PerspexProperty property, PerspexObject source, PerspexProperty sourceProperty, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public IDisposable BindTwoWay(PerspexProperty property, ISubject<object> source, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
}
public class TestLogical1 : TestLogical

53
tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs

@ -5,11 +5,10 @@ using System;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using Perspex.Collections;
using Perspex.Controls;
using Perspex.Styling;
using Perspex.Data;
using Xunit;
namespace Perspex.Styling.UnitTests
@ -110,6 +109,8 @@ namespace Perspex.Styling.UnitTests
Classes = new Classes();
}
public event EventHandler<PerspexPropertyChangedEventArgs> PropertyChanged;
public Classes Classes { get; }
public string Name { get; set; }
@ -124,44 +125,31 @@ namespace Perspex.Styling.UnitTests
public ITemplatedControl TemplatedParent { get; }
public IPropertyBag InheritanceParent
{
get
{
throw new NotImplementedException();
}
}
IPerspexReadOnlyList<string> IStyleable.Classes => Classes;
IObservable<Unit> IStyleable.StyleDetach { get; }
public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority)
{
throw new NotImplementedException();
}
public void SetValue(PerspexProperty property, object value, BindingPriority priority)
public object GetValue(PerspexProperty property)
{
throw new NotImplementedException();
}
public IObservable<object> GetObservable(PerspexProperty property)
public T GetValue<T>(PerspexProperty<T> property)
{
throw new NotImplementedException();
}
public bool IsRegistered(PerspexProperty property)
public void SetValue(PerspexProperty property, object value, BindingPriority priority)
{
throw new NotImplementedException();
}
public void ClearValue(PerspexProperty property)
public void SetValue<T>(PerspexProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public object GetValue(PerspexProperty property)
public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority)
{
throw new NotImplementedException();
}
@ -175,31 +163,6 @@ namespace Perspex.Styling.UnitTests
{
throw new NotImplementedException();
}
public IObservable<T> GetObservable<T>(PerspexProperty<T> property)
{
throw new NotImplementedException();
}
public T GetValue<T>(PerspexProperty<T> property)
{
throw new NotImplementedException();
}
public void SetValue<T>(PerspexProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public IDisposable BindTwoWay(PerspexProperty property, PerspexObject source, PerspexProperty sourceProperty, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public IDisposable BindTwoWay(PerspexProperty property, ISubject<object> source, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
}
public class TestLogical1 : TestLogical

28
tests/Perspex.Styling.UnitTests/SetterTests.cs

@ -0,0 +1,28 @@
// 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.Reactive.Subjects;
using Moq;
using Perspex.Controls;
using Perspex.Data;
using Xunit;
namespace Perspex.Styling.UnitTests
{
public class SetterTests
{
[Fact]
public void Setter_Should_Apply_Binding_To_Property()
{
var control = new TextBlock();
var subject = new BehaviorSubject<object>("foo");
var binding = Mock.Of<IBinding>(x => x.CreateSubject(control, TextBlock.TextProperty) == subject);
var style = Mock.Of<IStyle>();
var setter = new Setter(TextBlock.TextProperty, binding);
setter.Apply(style, control, null);
Assert.Equal("foo", control.Text);
}
}
}

46
tests/Perspex.Styling.UnitTests/StyleTests.cs

@ -141,52 +141,6 @@ namespace Perspex.Styling.UnitTests
Assert.Equal(new[] { "foodefault", "Foo", "Bar", "foodefault" }, values);
}
[Fact]
public void Style_With_ObservableSetter_Should_Update_Value()
{
var source = new BehaviorSubject<string>("Foo");
Style style = new Style(x => x.OfType<Class1>())
{
Setters = new[]
{
new ObservableSetter(Class1.FooProperty, source),
},
};
var target = new Class1();
style.Attach(target, null);
Assert.Equal("Foo", target.Foo);
}
[Fact]
public void Style_With_ObservableSetter_Should_Update_And_Restore_Value()
{
var source = new BehaviorSubject<string>("Foo");
var style = new Style(x => x.OfType<Class1>().Class("foo"))
{
Setters = new[]
{
new ObservableSetter(Class1.FooProperty, source),
},
};
var target = new Class1();
style.Attach(target, null);
Assert.Equal("foodefault", target.Foo);
target.Classes.Add("foo");
Assert.Equal("Foo", target.Foo);
source.OnNext("Bar");
Assert.Equal("Bar", target.Foo);
target.Classes.Remove("foo");
Assert.Equal("foodefault", target.Foo);
}
[Fact]
public void Style_Should_Detach_When_Removed_From_Logical_Tree()
{

54
tests/Perspex.Styling.UnitTests/TestControlBase.cs

@ -3,9 +3,9 @@
using System;
using System.Reactive;
using System.Reactive.Subjects;
using Perspex.Collections;
using Perspex.Controls;
using Perspex.Data;
namespace Perspex.Styling.UnitTests
{
@ -17,6 +17,8 @@ namespace Perspex.Styling.UnitTests
SubscribeCheckObservable = new TestObservable();
}
public event EventHandler<PerspexPropertyChangedEventArgs> PropertyChanged;
public string Name { get; set; }
public virtual Classes Classes { get; set; }
@ -31,79 +33,41 @@ namespace Perspex.Styling.UnitTests
set;
}
public IPropertyBag InheritanceParent
{
get
{
throw new NotImplementedException();
}
}
IPerspexReadOnlyList<string> IStyleable.Classes => Classes;
IObservable<Unit> IStyleable.StyleDetach { get; }
public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority)
{
throw new NotImplementedException();
}
public void SetValue(PerspexProperty property, object value, BindingPriority priority)
{
throw new NotImplementedException();
}
public IObservable<object> GetObservable(PerspexProperty property)
{
throw new NotImplementedException();
}
public bool IsRegistered(PerspexProperty property)
{
throw new NotImplementedException();
}
public void ClearValue(PerspexProperty property)
{
throw new NotImplementedException();
}
public object GetValue(PerspexProperty property)
{
throw new NotImplementedException();
}
public bool IsSet(PerspexProperty property)
{
throw new NotImplementedException();
}
public IDisposable Bind<T>(PerspexProperty<T> property, IObservable<T> source, BindingPriority priority = BindingPriority.LocalValue)
public T GetValue<T>(PerspexProperty<T> property)
{
throw new NotImplementedException();
}
public IObservable<T> GetObservable<T>(PerspexProperty<T> property)
public void SetValue(PerspexProperty property, object value, BindingPriority priority)
{
throw new NotImplementedException();
}
public T GetValue<T>(PerspexProperty<T> property)
public void SetValue<T>(PerspexProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public void SetValue<T>(PerspexProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
public bool IsSet(PerspexProperty property)
{
throw new NotImplementedException();
}
public IDisposable BindTwoWay(PerspexProperty property, PerspexObject source, PerspexProperty sourceProperty, BindingPriority priority = BindingPriority.LocalValue)
public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority)
{
throw new NotImplementedException();
}
public IDisposable BindTwoWay(PerspexProperty property, ISubject<object> source, BindingPriority priority = BindingPriority.LocalValue)
public IDisposable Bind<T>(PerspexProperty<T> property, IObservable<T> source, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}

58
tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs

@ -2,16 +2,17 @@
// 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;
using System.Reactive.Subjects;
using Perspex.Collections;
using Perspex.Controls;
using Perspex.Data;
namespace Perspex.Styling.UnitTests
{
public abstract class TestTemplatedControl : ITemplatedControl, IStyleable
{
public event EventHandler<PerspexPropertyChangedEventArgs> PropertyChanged;
public abstract Classes Classes
{
get;
@ -32,29 +33,16 @@ namespace Perspex.Styling.UnitTests
get;
}
public abstract IEnumerable<IVisual> VisualChildren
{
get;
}
public IPropertyBag InheritanceParent
{
get
{
throw new NotImplementedException();
}
}
IPerspexReadOnlyList<string> IStyleable.Classes => Classes;
IObservable<Unit> IStyleable.StyleDetach { get; }
public IObservable<T> GetObservable<T>(PerspexProperty<T> property)
public object GetValue(PerspexProperty property)
{
throw new NotImplementedException();
}
public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority)
public T GetValue<T>(PerspexProperty<T> property)
{
throw new NotImplementedException();
}
@ -64,27 +52,12 @@ namespace Perspex.Styling.UnitTests
throw new NotImplementedException();
}
public IObservable<object> GetObservable(PerspexProperty property)
{
throw new NotImplementedException();
}
public bool IsRegistered(PerspexProperty property)
{
throw new NotImplementedException();
}
public void ClearValue(PerspexProperty property)
{
throw new NotImplementedException();
}
public object GetValue(PerspexProperty property)
public void SetValue<T>(PerspexProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public bool IsSet(PerspexProperty property)
public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority)
{
throw new NotImplementedException();
}
@ -94,22 +67,7 @@ namespace Perspex.Styling.UnitTests
throw new NotImplementedException();
}
public T GetValue<T>(PerspexProperty<T> property)
{
throw new NotImplementedException();
}
public void SetValue<T>(PerspexProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public IDisposable BindTwoWay(PerspexProperty property, PerspexObject source, PerspexProperty sourceProperty, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public IDisposable BindTwoWay(PerspexProperty property, ISubject<object> source, BindingPriority priority = BindingPriority.LocalValue)
public bool IsSet(PerspexProperty property)
{
throw new NotImplementedException();
}

Loading…
Cancel
Save