Browse Source

Merge branch 'binding-refactor'

pull/396/head
Steven Kirk 11 years ago
parent
commit
5957d33d75
  1. 5
      samples/TestApplicationShared/App.cs
  2. 2
      samples/TestApplicationShared/GalleryStyle.cs
  3. 1
      samples/TestApplicationShared/MainWindow.cs
  4. 34
      samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs
  5. 11
      samples/XamlTestApplicationPcl/ViewModels/TestNode.cs
  6. 9
      samples/XamlTestApplicationPcl/Views/MainWindow.paml
  7. 16
      samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj
  8. 5
      samples/XamlTestApplicationPcl/packages.config
  9. 4
      src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs
  10. 53
      src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs
  11. 94
      src/Markup/Perspex.Markup.Xaml/Data/Binding.cs
  12. 35
      src/Markup/Perspex.Markup.Xaml/Data/IXamlBinding.cs
  13. 26
      src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs
  14. 3
      src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  15. 4
      src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
  16. 1
      src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
  17. 25
      src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs
  18. 11
      src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs
  19. 1
      src/Perspex.Animation/Animatable.cs
  20. 5
      src/Perspex.Animation/Animate.cs
  21. 20
      src/Perspex.Base/Data/AssignBindingAttribute.cs
  22. 36
      src/Perspex.Base/Data/BindingMode.cs
  23. 2
      src/Perspex.Base/Data/BindingPriority.cs
  24. 33
      src/Perspex.Base/Data/IBinding.cs
  25. 45
      src/Perspex.Base/Data/IndexerDescriptor.cs
  26. 2
      src/Perspex.Base/Diagnostics/PerspexObjectExtensions.cs
  27. 2
      src/Perspex.Base/Diagnostics/PerspexPropertyValue.cs
  28. 95
      src/Perspex.Base/IObservablePropertyBag.cs
  29. 61
      src/Perspex.Base/IPerspexObject.cs
  30. 12
      src/Perspex.Base/Perspex.Base.csproj
  31. 164
      src/Perspex.Base/PerspexObject.cs
  32. 203
      src/Perspex.Base/PerspexObjectExtensions.cs
  33. 19
      src/Perspex.Base/PerspexProperty.cs
  34. 2
      src/Perspex.Base/PerspexPropertyChangedEventArgs.cs
  35. 1
      src/Perspex.Base/PerspexProperty`1.cs
  36. 16
      src/Perspex.Base/Reactive/AnonymousSubject`1.cs
  37. 49
      src/Perspex.Base/Reactive/AnonymousSubject`2.cs
  38. 1
      src/Perspex.Controls/Control.cs
  39. 31
      src/Perspex.Controls/ControlExtensions.cs
  40. 1
      src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs
  41. 4
      src/Perspex.Controls/Presenters/ScrollContentPresenter.cs
  42. 8
      src/Perspex.Controls/Presenters/TextPresenter.cs
  43. 2
      src/Perspex.Controls/Primitives/AccessText.cs
  44. 9
      src/Perspex.Controls/Primitives/ScrollBar.cs
  45. 1
      src/Perspex.Controls/Primitives/SelectingItemsControl.cs
  46. 5
      src/Perspex.Controls/Primitives/TemplatedControl.cs
  47. 15
      src/Perspex.Controls/Primitives/ToggleButton.cs
  48. 2
      src/Perspex.Controls/Primitives/Track.cs
  49. 2
      src/Perspex.Controls/RadioButton.cs
  50. 10
      src/Perspex.Controls/ScrollViewer.cs
  51. 63
      src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs
  52. 55
      src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs
  53. 7
      src/Perspex.Controls/Templates/ITreeDataTemplate.cs
  54. 9
      src/Perspex.Controls/TextBlock.cs
  55. 5
      src/Perspex.Controls/TextBox.cs
  56. 4
      src/Perspex.Controls/TopLevel.cs
  57. 1
      src/Perspex.Diagnostics/Debug.cs
  58. 2
      src/Perspex.Diagnostics/DevTools.cs
  59. 1
      src/Perspex.Diagnostics/ViewModels/PropertyDetails.cs
  60. 2
      src/Perspex.Diagnostics/Views/ControlDetailsView.cs
  61. 2
      src/Perspex.Diagnostics/Views/LogicalTreeView.cs
  62. 2
      src/Perspex.Diagnostics/Views/VisualTreeView.cs
  63. 4
      src/Perspex.SceneGraph/Animation/CrossFade.cs
  64. 2
      src/Perspex.SceneGraph/Media/MatrixTransform.cs
  65. 2
      src/Perspex.SceneGraph/Media/RotateTransform.cs
  66. 4
      src/Perspex.SceneGraph/Media/TranslateTransform.cs
  67. 1
      src/Perspex.SceneGraph/Visual.cs
  68. 5
      src/Perspex.Styling/Perspex.Styling.csproj
  69. 74
      src/Perspex.Styling/Styling/ActivatedObservable.cs
  70. 111
      src/Perspex.Styling/Styling/ActivatedSubject.cs
  71. 72
      src/Perspex.Styling/Styling/ActivatedValue.cs
  72. 2
      src/Perspex.Styling/Styling/IStyleable.cs
  73. 9
      src/Perspex.Styling/Styling/ITemplatedControl.cs
  74. 65
      src/Perspex.Styling/Styling/ObservableSetter.cs
  75. 2
      src/Perspex.Styling/Styling/Selectors.cs
  76. 64
      src/Perspex.Styling/Styling/Setter.cs
  77. 107
      src/Perspex.Styling/Styling/StyleBinding.cs
  78. 1
      tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj
  79. 37
      tests/Perspex.Base.UnitTests/PerspexObjectTests_Binding.cs
  80. 1
      tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs
  81. 49
      tests/Perspex.Base.UnitTests/PerspexObjectTests_GetSubject.cs
  82. 1
      tests/Perspex.Base.UnitTests/PerspexObjectTests_SetValue.cs
  83. 1
      tests/Perspex.Base.UnitTests/PerspexPropertyTests.cs
  84. 7
      tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  85. 6
      tests/Perspex.LeakTests/ControlTests.cs
  86. 134
      tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs
  87. 8
      tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs
  88. 45
      tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
  89. 7
      tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs
  90. 5
      tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj
  91. 120
      tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs
  92. 34
      tests/Perspex.Markup.Xaml.UnitTests/Templates/TreeDataTemplateTests.cs
  93. 70
      tests/Perspex.Styling.UnitTests/ActivatedObservableTests.cs
  94. 98
      tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs
  95. 42
      tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs
  96. 5
      tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj
  97. 54
      tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs
  98. 53
      tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs
  99. 28
      tests/Perspex.Styling.UnitTests/SetterTests.cs
  100. 79
      tests/Perspex.Styling.UnitTests/StyleBindingTests.cs

5
samples/TestApplicationShared/App.cs

@ -21,11 +21,8 @@ namespace TestApplication
{
new FuncTreeDataTemplate<Node>(
x => new TextBlock {Text = x.Name},
x => x.Children,
x => true),
x => x.Children),
};
}
}
}

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;

34
samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs

@ -1,7 +1,9 @@
// 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 ReactiveUI;
namespace XamlTestApplication.ViewModels
{
@ -22,6 +24,7 @@ namespace XamlTestApplication.ViewModels
{
Header = "Root",
SubHeader = "Root Item",
IsExpanded = true,
Children = new[]
{
new TestNode
@ -33,6 +36,7 @@ namespace XamlTestApplication.ViewModels
{
Header = "Child 2",
SubHeader = "Child 2 Value",
IsExpanded = false,
Children = new[]
{
new TestNode
@ -50,9 +54,39 @@ namespace XamlTestApplication.ViewModels
}
}
};
CollapseNodesCommand = ReactiveCommand.Create();
CollapseNodesCommand.Subscribe(_ => ExpandNodes(false));
ExpandNodesCommand = ReactiveCommand.Create();
ExpandNodesCommand.Subscribe(_ => ExpandNodes(true));
}
public List<TestItem> Items { get; }
public List<TestNode> Nodes { get; }
public ReactiveCommand<object> CollapseNodesCommand { get; }
public ReactiveCommand<object> ExpandNodesCommand { get; }
public void ExpandNodes(bool expanded)
{
foreach (var node in Nodes)
{
ExpandNodes(node, expanded);
}
}
private void ExpandNodes(TestNode node, bool expanded)
{
node.IsExpanded = expanded;
if (node.Children != null)
{
foreach (var child in node.Children)
{
ExpandNodes(child, expanded);
}
}
}
}
}

11
samples/XamlTestApplicationPcl/ViewModels/TestNode.cs

@ -2,13 +2,22 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using ReactiveUI;
namespace XamlTestApplication.ViewModels
{
public class TestNode
public class TestNode : ReactiveObject
{
private bool _isExpanded;
public string Header { get; set; }
public string SubHeader { get; set; }
public IEnumerable<TestNode> Children { get; set; }
public bool IsExpanded
{
get { return _isExpanded; }
set { this.RaiseAndSetIfChanged(ref this._isExpanded, value); }
}
}
}

9
samples/XamlTestApplicationPcl/Views/MainWindow.paml

@ -147,6 +147,11 @@
</StackPanel>
</DropDown>
<TreeView Items="{Binding Nodes}">
<TreeView.Styles>
<Style Selector="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
</TreeView.Styles>
<TreeView.DataTemplates>
<TreeDataTemplate DataType="vm:TestNode" ItemsSource="{Binding Children}">
<StackPanel>
@ -156,6 +161,10 @@
</TreeDataTemplate>
</TreeView.DataTemplates>
</TreeView>
<StackPanel Orientation="Vertical" Gap="4">
<Button Command="{Binding CollapseNodesCommand}">Collapse Nodes</Button>
<Button Command="{Binding ExpandNodesCommand}">Expand Nodes</Button>
</StackPanel>
</StackPanel>
</TabItem>
<TabItem Header="Layout">

16
samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj

@ -123,6 +123,22 @@
<HintPath>..\..\packages\Splat.1.6.2\lib\Portable-net45+win+wpa81+wp80\Splat.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Rx-Interfaces.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Interfaces.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Rx-Linq.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Linq.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Rx-PlatformServices.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.PlatformServices.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="XamlTestApp.paml">

5
samples/XamlTestApplicationPcl/packages.config

@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Rx-Core" version="2.2.5" targetFramework="portable45-net45+win8" />
<package id="Rx-Interfaces" version="2.2.5" targetFramework="portable45-net45+win8" />
<package id="Rx-Linq" version="2.2.5" targetFramework="portable45-net45+win8" />
<package id="Rx-Main" version="2.2.5" targetFramework="portable45-net45+win8" />
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="portable45-net45+win8" />
<package id="Splat" version="1.6.2" targetFramework="portable45-net45+win8" />
</packages>

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)

94
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,66 +51,41 @@ 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)
{
observer = CreateElementSubject(
observer = CreateElementObserver(
(IControl)target,
pathInfo.ElementName ?? ElementName,
pathInfo.Path);
}
else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext)
{
observer = CreateDataContextSubject(
observer = CreateDataContexObserver(
target,
pathInfo.Path,
targetIsDataContext);
}
else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
{
observer = CreateTemplatedParentSubject(
observer = CreateTemplatedParentObserver(
target,
pathInfo.Path);
}
@ -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();
@ -208,8 +148,8 @@ namespace Perspex.Markup.Xaml.Data
}
}
private ExpressionObserver CreateDataContextSubject(
IObservablePropertyBag target,
private ExpressionObserver CreateDataContexObserver(
IPerspexObject target,
string path,
bool targetIsDataContext)
{
@ -231,15 +171,15 @@ namespace Perspex.Markup.Xaml.Data
{
return new ExpressionObserver(
target.GetObservable(Visual.VisualParentProperty)
.OfType<IObservablePropertyBag>()
.OfType<IPerspexObject>()
.Select(x => x.GetObservable(Control.DataContextProperty))
.Switch(),
path);
}
}
private ExpressionObserver CreateTemplatedParentSubject(
IObservablePropertyBag target,
private ExpressionObserver CreateTemplatedParentObserver(
IPerspexObject target,
string path)
{
Contract.Requires<ArgumentNullException>(target != null);
@ -256,7 +196,7 @@ namespace Perspex.Markup.Xaml.Data
return result;
}
private ExpressionObserver CreateElementSubject(
private ExpressionObserver CreateElementObserver(
IControl target,
string elementName,
string path)

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;

3
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
@ -26,6 +27,7 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
ElementName = ElementName,
Mode = Mode,
Path = Path,
Priority = Priority,
};
}
@ -34,5 +36,6 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
public string ElementName { get; set; }
public BindingMode Mode { get; set; }
public string Path { get; set; }
public BindingPriority Priority { get; set; } = BindingPriority.LocalValue;
}
}

4
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
@ -24,9 +25,9 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
Converter = Converter,
ElementName = ElementName,
Mode = Mode,
Priority = BindingPriority.TemplatedParent,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Path = Path,
Priority = Priority,
};
}
@ -34,5 +35,6 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
public string ElementName { get; set; }
public BindingMode Mode { get; set; }
public string Path { get; set; }
public BindingPriority Priority { get; set; } = BindingPriority.TemplatedParent;
}
}

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>

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

@ -4,8 +4,10 @@
using System;
using System.Collections;
using System.Reactive.Linq;
using System.Reflection;
using Perspex.Controls;
using Perspex.Controls.Templates;
using Perspex.Data;
using Perspex.Markup.Data;
using Perspex.Markup.Xaml.Data;
using Perspex.Metadata;
@ -19,16 +21,19 @@ namespace Perspex.Markup.Xaml.Templates
[Content]
public TemplateContent Content { get; set; }
[AssignBinding]
public Binding ItemsSource { get; set; }
public bool Match(object data)
{
if (DataType == null)
{
throw new InvalidOperationException("DataTemplate must have a DataType.");
return true;
}
else
{
return DataType.GetTypeInfo().IsAssignableFrom(data.GetType().GetTypeInfo());
}
return DataType == data.GetType();
}
public IEnumerable ItemsSelector(object item)

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);
}
}

12
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" />
@ -74,6 +76,8 @@
<Compile Include="PriorityLevel.cs" />
<Compile Include="PriorityValue.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Reactive\AnonymousSubject`1.cs" />
<Compile Include="Reactive\AnonymousSubject`2.cs" />
<Compile Include="Reactive\PerspexObservable.cs" />
<Compile Include="Threading\Dispatcher.cs" />
<Compile Include="Threading\DispatcherPriority.cs" />

164
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,18 +182,19 @@ 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);
var subject = sourceBinding.Source.GetSubject(sourceBinding.Property, sourceBinding.Priority);
this.Bind(binding.Property, subject, BindingMode.TwoWay, sourceBinding.Priority);
break;
}
}
}
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 +218,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>
@ -561,67 +481,6 @@ namespace Perspex
}
}
/// <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>
public IDisposable BindTwoWay(
PerspexProperty property,
PerspexObject source,
PerspexProperty sourceProperty,
BindingPriority priority = BindingPriority.LocalValue)
{
VerifyAccess();
_propertyLog.Verbose(
"Bound two way {Property} to {Binding} with priority {Priority}",
property,
source,
priority);
return new CompositeDisposable(
Bind(property, source.GetObservable(sourceProperty)),
source.Bind(sourceProperty, GetObservable(property)));
}
/// <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>
public IDisposable BindTwoWay(
PerspexProperty property,
ISubject<object> source,
BindingPriority priority = BindingPriority.LocalValue)
{
VerifyAccess();
_propertyLog.Verbose(
"Bound two way {Property} to {Binding} with priority {Priority}",
property,
GetDescription(source),
priority);
return new CompositeDisposable(
Bind(property, source),
GetObservable(property).Subscribe(source));
}
/// <summary>
/// Forces the specified property to be revalidated.
/// </summary>
@ -638,11 +497,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>

203
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,192 @@ 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>
/// <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{Object}"/> which can be used for two-way binding to/from the
/// property.
/// </returns>
public static ISubject<object> GetSubject(
this IPerspexObject o,
PerspexProperty property,
BindingPriority priority = BindingPriority.LocalValue)
{
// TODO: Subject.Create<T> is not yet in stable Rx : once it is, remove the
// AnonymousSubject classes and use Subject.Create<T>.
var output = new Subject<object>();
var result = new AnonymousSubject<object>(
Observer.Create<object>(
x => output.OnNext(x),
e => output.OnError(e),
() => output.OnCompleted()),
o.GetObservable(property));
o.Bind(property, output, priority);
return result;
}
/// <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 +242,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}})"/>.

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
{

16
src/Perspex.Base/Reactive/AnonymousSubject`1.cs

@ -0,0 +1,16 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Subjects;
namespace Perspex.Reactive
{
public class AnonymousSubject<T> : AnonymousSubject<T, T>, ISubject<T>
{
public AnonymousSubject(IObserver<T> observer, IObservable<T> observable)
: base(observer, observable)
{
}
}
}

49
src/Perspex.Base/Reactive/AnonymousSubject`2.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.Reactive.Subjects;
namespace Perspex.Reactive
{
public 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);
}
}
}

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>

1
src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs

@ -77,7 +77,6 @@ namespace Perspex.Controls.Generators
result.SetValue(ContentProperty, template.Build(item));
result.SetValue(ItemsProperty, template.ItemsSelector(item));
result.SetValue(IsExpandedProperty, template.IsExpanded(item));
if (!(item is IControl))
{

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);

15
src/Perspex.Controls/Primitives/ToggleButton.cs

@ -9,21 +9,22 @@ namespace Perspex.Controls.Primitives
public class ToggleButton : Button
{
public static readonly PerspexProperty<bool> IsCheckedProperty =
PerspexProperty.Register<ToggleButton, bool>("IsChecked");
PerspexProperty.RegisterDirect<ToggleButton, bool>(
"IsChecked",
o => o.IsChecked,
(o,v) => o.IsChecked = v);
private bool _isChecked;
static ToggleButton()
{
PseudoClass(IsCheckedProperty, ":checked");
}
public ToggleButton()
{
}
public bool IsChecked
{
get { return GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
get { return _isChecked; }
set { SetAndRaise(IsCheckedProperty, ref _isChecked, value); }
}
protected override void OnClick(RoutedEventArgs e)

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);
}

63
src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs

@ -14,8 +14,6 @@ namespace Perspex.Controls.Templates
{
private readonly Func<object, IEnumerable> _itemsSelector;
private readonly Func<object, bool> _isExpanded;
/// <summary>
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate"/> class.
/// </summary>
@ -35,30 +33,6 @@ namespace Perspex.Controls.Templates
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate"/> class.
/// </summary>
/// <param name="type">The type of data which the data template matches.</param>
/// <param name="build">
/// A function which when passed an object of <paramref name="type"/> returns a control.
/// </param>
/// <param name="itemsSelector">
/// A function which when passed an object of <paramref name="type"/> returns the child
/// items.
/// </param>
/// <param name="isExpanded">
/// A function which when passed an object of <paramref name="type"/> returns the the
/// initial expanded state of the node.
/// </param>
public FuncTreeDataTemplate(
Type type,
Func<object, IControl> build,
Func<object, IEnumerable> itemsSelector,
Func<object, bool> isExpanded)
: this(o => IsInstance(o, type), build, itemsSelector, isExpanded)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate"/> class.
/// </summary>
@ -75,46 +49,9 @@ namespace Perspex.Controls.Templates
Func<object, bool> match,
Func<object, IControl> build,
Func<object, IEnumerable> itemsSelector)
: this(match, build, itemsSelector, _ => false)
{
_itemsSelector = itemsSelector;
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate"/> class.
/// </summary>
/// <param name="match">
/// A function which determines whether the data template matches the specified data.
/// </param>
/// <param name="build">
/// A function which when passed a matching object returns a control.
/// </param>
/// <param name="itemsSelector">
/// A function which when passed a matching object returns the child items.
/// </param>
/// <param name="isExpanded">
/// A function which when passed a matching object returns the the initial expanded state
/// of the node.
/// </param>
public FuncTreeDataTemplate(
Func<object, bool> match,
Func<object, IControl> build,
Func<object, IEnumerable> itemsSelector,
Func<object, bool> isExpanded)
: base(match, build)
{
_itemsSelector = itemsSelector;
_isExpanded = isExpanded;
}
/// <summary>
/// Checks to see if the item should be initially expanded.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>True if the item should be initially expanded, otherwise false.</returns>
public bool IsExpanded(object item)
{
return this?._isExpanded(item) ?? false;
}
/// <summary>

55
src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs

@ -32,32 +32,6 @@ namespace Perspex.Controls.Templates
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate{T}"/> class.
/// </summary>
/// <param name="build">
/// A function which when passed an object of <typeparamref name="T"/> returns a control.
/// </param>
/// <param name="itemsSelector">
/// A function which when passed an object of <typeparamref name="T"/> returns the child
/// items.
/// </param>
/// <param name="isExpanded">
/// A function which when passed an object of <typeparamref name="T"/> returns the the
/// initial expanded state of the node.
/// </param>
public FuncTreeDataTemplate(
Func<T, Control> build,
Func<T, IEnumerable> itemsSelector,
Func<T, bool> isExpanded)
: base(
typeof(T),
Cast(build),
Cast(itemsSelector),
Cast(isExpanded))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate{T}"/> class.
/// </summary>
@ -81,35 +55,6 @@ namespace Perspex.Controls.Templates
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate{T}"/> class.
/// </summary>
/// <param name="match">
/// A function which determines whether the data template matches the specified data.
/// </param>
/// <param name="build">
/// A function which when passed a matching object returns a control.
/// </param>
/// <param name="itemsSelector">
/// A function which when passed a matching object returns the child items.
/// </param>
/// <param name="isExpanded">
/// A function which when passed a matching object returns the the initial expanded state
/// of the node.
/// </param>
public FuncTreeDataTemplate(
Func<T, bool> match,
Func<T, Control> build,
Func<T, IEnumerable> itemsSelector,
Func<T, bool> isExpanded)
: base(
CastMatch(match),
Cast(build),
Cast(itemsSelector),
Cast(isExpanded))
{
}
/// <summary>
/// Casts a typed match function to an untyped match function.
/// </summary>

7
src/Perspex.Controls/Templates/ITreeDataTemplate.cs

@ -10,13 +10,6 @@ namespace Perspex.Controls.Templates
/// </summary>
public interface ITreeDataTemplate : IDataTemplate
{
/// <summary>
/// Checks to see if the item should be initially expanded.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>True if the item should be initially expanded, otherwise false.</returns>
bool IsExpanded(object item);
/// <summary>
/// Selects the child items of an item.
/// </summary>

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;

5
src/Perspex.Styling/Perspex.Styling.csproj

@ -45,6 +45,8 @@
<Compile Include="ILogical.cs" />
<Compile Include="LogicalTree\LogicalExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Styling\ActivatedSubject.cs" />
<Compile Include="Styling\ActivatedValue.cs" />
<Compile Include="Styling\IGlobalStyles.cs" />
<Compile Include="Styling\ISetter.cs" />
<Compile Include="Styling\IStyle.cs" />
@ -56,11 +58,10 @@
<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" />
<Compile Include="Styling\StyleBinding.cs" />
<Compile Include="Styling\ActivatedObservable.cs" />
<Compile Include="Styling\Styler.cs" />
<Compile Include="Styling\Styles.cs" />
</ItemGroup>

74
src/Perspex.Styling/Styling/ActivatedObservable.cs

@ -0,0 +1,74 @@
// 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;
using System.Reactive.Linq;
namespace Perspex.Styling
{
/// <summary>
/// An observable which is switched on or off according to an activator observable.
/// </summary>
/// <remarks>
/// An <see cref="ActivatedObservable"/> has two inputs: an activator observable a
/// <see cref="Source"/> observable which produces the activated value. When the activator
/// produces true, the <see cref="ActivatedObservable"/> will produce the current activated
/// value. When the activator produces false it will produce
/// <see cref="PerspexProperty.UnsetValue"/>.
/// </remarks>
internal class ActivatedObservable : ObservableBase<object>, IDescription
{
/// <summary>
/// Initializes a new instance of the <see cref="ActivatedObservable"/> class.
/// </summary>
/// <param name="activator">The activator.</param>
/// <param name="source">An observable that produces the activated value.</param>
/// <param name="description">The binding description.</param>
public ActivatedObservable(
IObservable<bool> activator,
IObservable<object> source,
string description)
{
Activator = activator;
Description = description;
Source = source;
}
/// <summary>
/// Gets the activator observable.
/// </summary>
public IObservable<bool> Activator { get; }
/// <summary>
/// Gets a description of the binding.
/// </summary>
public string Description { get; }
/// <summary>
/// Gets an observable which produces the <see cref="ActivatedValue"/>.
/// </summary>
public IObservable<object> Source { get; }
/// <summary>
/// Notifies the provider that an observer is to receive notifications.
/// </summary>
/// <param name="observer">The observer.</param>
/// <returns>IDisposable object used to unsubscribe from the observable sequence.</returns>
protected override IDisposable SubscribeCore(IObserver<object> observer)
{
Contract.Requires<ArgumentNullException>(observer != null);
var sourceCompleted = Source.TakeLast(1).Select(_ => Unit.Default);
var activatorCompleted = Activator.TakeLast(1).Select(_ => Unit.Default);
var completed = sourceCompleted.Merge(activatorCompleted);
return Activator
.CombineLatest(Source, (x, y) => new { Active = x, Value = y })
.Select(x => x.Active ? x.Value : PerspexProperty.UnsetValue)
.DistinctUntilChanged()
.TakeUntil(completed)
.Subscribe(observer);
}
}
}

111
src/Perspex.Styling/Styling/ActivatedSubject.cs

@ -0,0 +1,111 @@
// 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.Linq;
using System.Reactive.Subjects;
namespace Perspex.Styling
{
/// <summary>
/// A subject which is switched on or off according to an activator observable.
/// </summary>
/// <remarks>
/// An <see cref="ActivatedSubject"/> has two inputs: an activator observable and either an
/// <see cref="ActivatedValue"/> or a <see cref="Source"/> observable which produces the
/// activated value. When the activator produces true, the <see cref="ActivatedObservable"/> will
/// produce the current activated value. When the activator produces false it will produce
/// <see cref="PerspexProperty.UnsetValue"/>.
/// </remarks>
internal class ActivatedSubject : ActivatedObservable, ISubject<object>, IDescription
{
private bool? _active;
private bool _completed;
private object _value;
/// <summary>
/// Initializes a new instance of the <see cref="ActivatedSubject"/> class.
/// </summary>
/// <param name="activator">The activator.</param>
/// <param name="source">An observable that produces the activated value.</param>
/// <param name="description">The binding description.</param>
public ActivatedSubject(
IObservable<bool> activator,
ISubject<object> source,
string description)
: base(activator, source, description)
{
Activator.Subscribe(ActivatorChanged, ActivatorError, ActivatorCompleted);
}
/// <summary>
/// Gets the underlying subject.
/// </summary>
public new ISubject<object> Source
{
get { return (ISubject<object>)base.Source; }
}
/// <summary>
/// Notifies all subscribed observers about the end of the sequence.
/// </summary>
public void OnCompleted()
{
if (_active.Value && !_completed)
{
Source.OnCompleted();
}
}
/// <summary>
/// Notifies all subscribed observers with the exception.
/// </summary>
/// <param name="error">The exception to send to all subscribed observers.</param>
/// <exception cref="ArgumentNullException"><paramref name="error"/> is null.</exception>
public void OnError(Exception error)
{
if (_active.Value && !_completed)
{
Source.OnError(error);
}
}
/// <summary>
/// Notifies all subscribed observers with the value.
/// </summary>
/// <param name="value">The value to send to all subscribed observers.</param>
public void OnNext(object value)
{
_value = value;
if (_active.Value && !_completed)
{
Source.OnNext(value);
}
}
private void ActivatorChanged(bool active)
{
bool first = !_active.HasValue;
_active = active;
if (!first)
{
Source.OnNext(active ? _value : PerspexProperty.UnsetValue);
}
}
private void ActivatorCompleted()
{
_completed = true;
Source.OnCompleted();
}
private void ActivatorError(Exception e)
{
_completed = true;
Source.OnError(e);
}
}
}

72
src/Perspex.Styling/Styling/ActivatedValue.cs

@ -0,0 +1,72 @@
// 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;
using System.Reactive.Linq;
namespace Perspex.Styling
{
/// <summary>
/// An value which is switched on or off according to an activator observable.
/// </summary>
/// <remarks>
/// An <see cref="ActivatedValue"/> has two inputs: an activator observable and an
/// <see cref="Value"/>. When the activator produces true, the
/// <see cref="ActivatedValue"/> will produce the current value. When the activator
/// produces false it will produce <see cref="PerspexProperty.UnsetValue"/>.
/// </remarks>
internal class ActivatedValue : ObservableBase<object>, IDescription
{
/// <summary>
/// The activator.
/// </summary>
private readonly IObservable<bool> _activator;
/// <summary>
/// Initializes a new instance of the <see cref="ActivatedObservable"/> class.
/// </summary>
/// <param name="activator">The activator.</param>
/// <param name="value">The activated value.</param>
/// <param name="description">The binding description.</param>
public ActivatedValue(
IObservable<bool> activator,
object value,
string description)
{
_activator = activator;
Value = value;
Description = description;
}
/// <summary>
/// Gets the activated value.
/// </summary>
public object Value
{
get;
}
/// <summary>
/// Gets a description of the binding.
/// </summary>
public string Description
{
get;
}
/// <summary>
/// Notifies the provider that an observer is to receive notifications.
/// </summary>
/// <param name="observer">The observer.</param>
/// <returns>IDisposable object used to unsubscribe from the observable sequence.</returns>
protected override IDisposable SubscribeCore(IObserver<object> observer)
{
Contract.Requires<ArgumentNullException>(observer != null);
return _activator
.Select(active => active ? Value : PerspexProperty.UnsetValue)
.Subscribe(observer);
}
}
}

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;
}

64
src/Perspex.Styling/Styling/Setter.cs

@ -2,7 +2,10 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Subjects;
using Perspex.Data;
using Perspex.Metadata;
using Perspex.Reactive;
namespace Perspex.Styling
{
@ -46,6 +49,7 @@ namespace Perspex.Styling
/// Gets or sets the property value.
/// </summary>
[Content]
[AssignBinding]
public object Value
{
get;
@ -60,15 +64,67 @@ namespace Perspex.Styling
/// <param name="activator">An optional activator.</param>
public void Apply(IStyle style, IStyleable control, IObservable<bool> activator)
{
if (activator == null)
Contract.Requires<ArgumentNullException>(control != null);
var description = style?.ToString();
if (Property == null)
{
throw new InvalidOperationException("Setter.Property must be set.");
}
var binding = Value as IBinding;
if (binding != null)
{
control.SetValue(Property, Value, BindingPriority.Style);
if (activator == null)
{
Bind(control, Property, binding);
}
else
{
var subject = binding.CreateSubject(control, Property);
var activated = new ActivatedSubject(activator, subject, description);
Bind(control, Property, binding, activated);
}
}
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 ActivatedValue(activator, Value, description);
control.Bind(Property, activated, BindingPriority.StyleTrigger);
}
}
}
private void Bind(IStyleable control, PerspexProperty property, IBinding binding)
{
Bind(control, property, binding, binding.CreateSubject(control, property));
}
private void Bind(
IStyleable control,
PerspexProperty property,
IBinding binding,
ISubject<object> subject)
{
var mode = binding.Mode;
if (mode == BindingMode.Default)
{
mode = property.DefaultBindingMode;
}
control.Bind(
property,
subject,
mode,
binding.Priority);
}
}
}

107
src/Perspex.Styling/Styling/StyleBinding.cs

@ -1,107 +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;
using System.Reactive.Linq;
namespace Perspex.Styling
{
/// <summary>
/// Provides an observable for a style.
/// </summary>
/// <remarks>
/// A <see cref="StyleBinding"/> has two inputs: an activator observable and either an
/// <see cref="ActivatedValue"/> or a <see cref="Source"/> observable which produces the
/// activated value. When the activator produces true, the <see cref="StyleBinding"/> will
/// produce the current activated value. When the activator produces false it will produce
/// <see cref="PerspexProperty.UnsetValue"/>.
/// </remarks>
internal class StyleBinding : ObservableBase<object>, IDescription
{
/// <summary>
/// The activator.
/// </summary>
private readonly IObservable<bool> _activator;
/// <summary>
/// Initializes a new instance of the <see cref="StyleBinding"/> class.
/// </summary>
/// <param name="activator">The activator.</param>
/// <param name="activatedValue">The activated value.</param>
/// <param name="description">The binding description.</param>
public StyleBinding(
IObservable<bool> activator,
object activatedValue,
string description)
{
_activator = activator;
ActivatedValue = activatedValue;
Description = description;
}
/// <summary>
/// Initializes a new instance of the <see cref="StyleBinding"/> class.
/// </summary>
/// <param name="activator">The activator.</param>
/// <param name="source">An observable that produces the activated value.</param>
/// <param name="description">The binding description.</param>
public StyleBinding(
IObservable<bool> activator,
IObservable<object> source,
string description)
{
_activator = activator;
Description = description;
Source = source;
}
/// <summary>
/// Gets the activated value.
/// </summary>
public object ActivatedValue
{
get;
}
/// <summary>
/// Gets a description of the binding.
/// </summary>
public string Description
{
get;
}
/// <summary>
/// Gets an observable which produces the <see cref="ActivatedValue"/>.
/// </summary>
public IObservable<object> Source
{
get;
}
/// <summary>
/// Notifies the provider that an observer is to receive notifications.
/// </summary>
/// <param name="observer">The observer.</param>
/// <returns>IDisposable object used to unsubscribe from the observable sequence.</returns>
protected override IDisposable SubscribeCore(IObserver<object> observer)
{
Contract.Requires<ArgumentNullException>(observer != null);
if (Source == null)
{
return _activator
.Select(active => active ? ActivatedValue : PerspexProperty.UnsetValue)
.Subscribe(observer);
}
else
{
return _activator
.CombineLatest(Source, (x, y) => new { Active = x, Value = y })
.Select(x => x.Active ? x.Value : PerspexProperty.UnsetValue)
.Subscribe(observer);
}
}
}
}

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" />

37
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
@ -81,7 +82,7 @@ namespace Perspex.Base.UnitTests
}
[Fact]
public void Two_Way_Binding_Works()
public void Two_Way_Separate_Binding_Works()
{
Class1 obj1 = new Class1();
Class1 obj2 = new Class1();
@ -142,34 +143,6 @@ namespace Perspex.Base.UnitTests
Assert.Equal("third", obj2.GetValue(Class1.FooProperty));
}
[Fact]
public void BindTwoWay_Gets_Initial_Value_From_Source()
{
Class1 source = new Class1();
Class1 target = new Class1();
source.SetValue(Class1.FooProperty, "initial");
target.BindTwoWay(Class1.FooProperty, source, Class1.FooProperty);
Assert.Equal("initial", target.GetValue(Class1.FooProperty));
}
[Fact]
public void BindTwoWay_Updates_Values()
{
Class1 source = new Class1();
Class1 target = new Class1();
source.SetValue(Class1.FooProperty, "first");
target.BindTwoWay(Class1.FooProperty, source, Class1.FooProperty);
Assert.Equal("first", target.GetValue(Class1.FooProperty));
source.SetValue(Class1.FooProperty, "second");
Assert.Equal("second", target.GetValue(Class1.FooProperty));
target.SetValue(Class1.FooProperty, "third");
Assert.Equal("third", source.GetValue(Class1.FooProperty));
}
[Fact]
public void Local_Binding_Overwrites_Local_Value()
{
@ -234,7 +207,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 +221,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 +237,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

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);

6
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.
@ -272,8 +273,7 @@ namespace Perspex.LeakTests
{
new FuncTreeDataTemplate<Node>(
x => new TextBlock { Text = x.Name },
x => x.Children,
x => true)
x => x.Children)
},
Items = nodes
}

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" />

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

@ -0,0 +1,120 @@
// 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 System.Reactive.Linq;
using Moq;
using Perspex.Controls;
using Perspex.Controls.Primitives;
using Perspex.Data;
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);
}
}
[Fact]
public void Setter_With_TwoWay_Binding_Should_Update_Source()
{
using (PerspexLocator.EnterScope())
{
PerspexLocator.CurrentMutable
.Bind<IPlatformThreadingInterface>()
.ToConstant(Mock.Of<IPlatformThreadingInterface>(x =>
x.CurrentThreadIsLoopThread == true));
var data = new Data
{
Foo = "foo",
};
var control = new TextBox
{
DataContext = data,
};
var setter = new Setter
{
Property = TextBox.TextProperty,
Value = new Binding
{
Path = "Foo",
Mode = BindingMode.TwoWay
}
};
setter.Apply(null, control, null);
Assert.Equal("foo", control.Text);
control.Text = "bar";
Assert.Equal("bar", data.Foo);
}
}
[Fact]
public void Setter_With_TwoWay_Binding_And_Activator_Should_Update_Source()
{
using (PerspexLocator.EnterScope())
{
PerspexLocator.CurrentMutable
.Bind<IPlatformThreadingInterface>()
.ToConstant(Mock.Of<IPlatformThreadingInterface>(x =>
x.CurrentThreadIsLoopThread == true));
var data = new Data
{
Foo = "foo",
};
var control = new TextBox
{
DataContext = data,
};
var setter = new Setter
{
Property = TextBox.TextProperty,
Value = new Binding
{
Path = "Foo",
Mode = BindingMode.TwoWay
}
};
var activator = Observable.Never<bool>().StartWith(true);
setter.Apply(null, control, activator);
Assert.Equal("foo", control.Text);
control.Text = "bar";
Assert.Equal("bar", data.Foo);
}
}
private class Data
{
public string Foo { get; set; }
}
}
}

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);
}
}
}
}

70
tests/Perspex.Styling.UnitTests/ActivatedObservableTests.cs

@ -0,0 +1,70 @@
// 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 System.Reactive.Subjects;
using Xunit;
namespace Perspex.Styling.UnitTests
{
public class ActivatedObservableTests
{
[Fact]
public void Should_Produce_Correct_Values()
{
var activator = new BehaviorSubject<bool>(false);
var source = new BehaviorSubject<object>(1);
var target = new ActivatedObservable(activator, source, string.Empty);
var result = new List<object>();
target.Subscribe(x => result.Add(x));
activator.OnNext(true);
source.OnNext(2);
activator.OnNext(false);
source.OnNext(3);
activator.OnNext(true);
Assert.Equal(
new[]
{
PerspexProperty.UnsetValue,
1,
2,
PerspexProperty.UnsetValue,
3,
},
result);
}
[Fact]
public void Should_Complete_When_Source_Completes()
{
var activator = new BehaviorSubject<bool>(false);
var source = new BehaviorSubject<object>(1);
var target = new ActivatedObservable(activator, source, string.Empty);
var completed = false;
target.Subscribe(_ => { }, () => completed = true);
source.OnCompleted();
Assert.True(completed);
}
[Fact]
public void Should_Complete_When_Activator_Completes()
{
var activator = new BehaviorSubject<bool>(false);
var source = new BehaviorSubject<object>(1);
var target = new ActivatedObservable(activator, source, string.Empty);
var completed = false;
target.Subscribe(_ => { }, () => completed = true);
activator.OnCompleted();
Assert.True(completed);
}
}
}

98
tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs

@ -0,0 +1,98 @@
// 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.Disposables;
using System.Reactive.Subjects;
using Xunit;
namespace Perspex.Styling.UnitTests
{
public class ActivatedSubjectTests
{
[Fact]
public void Should_Set_Values()
{
var activator = new BehaviorSubject<bool>(false);
var source = new TestSubject();
var target = new ActivatedSubject(activator, source, string.Empty);
target.OnNext("bar");
Assert.Equal(PerspexProperty.UnsetValue, source.Value);
activator.OnNext(true);
target.OnNext("baz");
Assert.Equal("baz", source.Value);
activator.OnNext(false);
Assert.Equal(PerspexProperty.UnsetValue, source.Value);
target.OnNext("bax");
activator.OnNext(true);
Assert.Equal("bax", source.Value);
}
[Fact]
public void Should_Invoke_OnCompleted_On_Activator_Completed()
{
var activator = new BehaviorSubject<bool>(false);
var source = new TestSubject();
var target = new ActivatedSubject(activator, source, string.Empty);
activator.OnCompleted();
Assert.True(source.Completed);
}
[Fact]
public void Should_Invoke_OnError_On_Activator_Error()
{
var activator = new BehaviorSubject<bool>(false);
var source = new TestSubject();
var target = new ActivatedSubject(activator, source, string.Empty);
activator.OnError(new Exception());
Assert.NotNull(source.Error);
}
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); }
}
}
private class TestSubject : ISubject<object>
{
private IObserver<object> _observer;
public bool Completed { get; set; }
public Exception Error { get; set; }
public object Value { get; set; } = PerspexProperty.UnsetValue;
public void OnCompleted()
{
Completed = true;
}
public void OnError(Exception error)
{
Error = error;
}
public void OnNext(object value)
{
Value = value;
}
public IDisposable Subscribe(IObserver<object> observer)
{
_observer = observer;
return Disposable.Empty;
}
}
}
}

42
tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs

@ -0,0 +1,42 @@
// 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 System.Reactive.Subjects;
using Xunit;
namespace Perspex.Styling.UnitTests
{
public class ActivatedValueTests
{
[Fact]
public void Should_Produce_Correct_Values()
{
var activator = new BehaviorSubject<bool>(false);
var target = new ActivatedValue(activator, 1, string.Empty);
var result = new List<object>();
target.Subscribe(x => result.Add(x));
activator.OnNext(true);
activator.OnNext(false);
Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, PerspexProperty.UnsetValue }, result);
}
[Fact]
public void Should_Complete_When_Activator_Completes()
{
var activator = new BehaviorSubject<bool>(false);
var target = new ActivatedValue(activator, 1, string.Empty);
var completed = false;
target.Subscribe(_ => { }, () => completed = true);
activator.OnCompleted();
Assert.True(completed);
}
}
}

5
tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj

@ -80,6 +80,8 @@
<Otherwise />
</Choose>
<ItemGroup>
<Compile Include="ActivatedValueTests.cs" />
<Compile Include="ActivatedSubjectTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SelectorTests_Class.cs" />
<Compile Include="SelectorTests_Child.cs" />
@ -90,7 +92,8 @@
<Compile Include="SelectorTests_OfType.cs" />
<Compile Include="SelectorTests_Template.cs" />
<Compile Include="StyleActivatorTests.cs" />
<Compile Include="StyleBindingTests.cs" />
<Compile Include="ActivatedObservableTests.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);
}
}
}

79
tests/Perspex.Styling.UnitTests/StyleBindingTests.cs

@ -1,79 +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.Collections.Generic;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Xunit;
namespace Perspex.Styling.UnitTests
{
public class StyleBindingTests
{
[Fact]
public async void Should_Produce_UnsetValue_On_Activator_False()
{
var activator = new BehaviorSubject<bool>(false);
var target = new StyleBinding(activator, 1, string.Empty);
var result = await target.Take(1);
Assert.Equal(PerspexProperty.UnsetValue, result);
}
[Fact]
public async void Should_Produce_Value_On_Activator_True()
{
var activator = new BehaviorSubject<bool>(true);
var target = new StyleBinding(activator, 1, string.Empty);
var result = await target.Take(1);
Assert.Equal(1, result);
}
[Fact]
public void Should_Change_Value_On_Activator_Change()
{
var activator = new BehaviorSubject<bool>(false);
var target = new StyleBinding(activator, 1, string.Empty);
var result = new List<object>();
target.Subscribe(x => result.Add(x));
activator.OnNext(true);
activator.OnNext(false);
Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, PerspexProperty.UnsetValue }, result);
}
[Fact]
public void Should_Change_Value_With_Source_Observable()
{
var activator = new BehaviorSubject<bool>(false);
var source = new BehaviorSubject<object>(1);
var target = new StyleBinding(activator, source, string.Empty);
var result = new List<object>();
target.Subscribe(x => result.Add(x));
activator.OnNext(true);
source.OnNext(2);
activator.OnNext(false);
Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, 2, PerspexProperty.UnsetValue }, result);
}
[Fact]
public void Should_Complete_When_Activator_Completes()
{
var activator = new BehaviorSubject<bool>(false);
var target = new StyleBinding(activator, 1, string.Empty);
var completed = false;
target.Subscribe(_ => { }, () => completed = true);
activator.OnCompleted();
Assert.True(completed);
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save