Browse Source

Merge branch 'bindings' into xaml-datatemplates

Conflicts:
	samples/XamlTestApplication/ViewModels/MainWindowViewModel.cs
	samples/XamlTestApplication/ViewModels/TestItem.cs
	samples/XamlTestApplication/Views/MainWindow.paml
	src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs
	src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBindingDefinition.cs
	src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs
pull/237/head
Steven Kirk 11 years ago
parent
commit
dbc32b17d1
  1. 2
      samples/XamlTestApplication/ViewModels/TestNode.cs
  2. 6
      samples/XamlTestApplication/Views/MainWindow.cs
  3. 2
      samples/XamlTestApplication/XamlTestApplication.csproj
  4. 22
      src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs
  5. 109
      src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/ObservablePropertyBranch.cs
  6. 52
      src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyMountPoint.cs
  7. 36
      src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyPath.cs
  8. 41
      src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/TargettedProperty.cs
  9. 165
      src/Markup/Perspex.Markup.Xaml/DataBinding/DataContextChangeSynchronizer.cs
  10. 3
      src/Markup/Perspex.Markup.Xaml/DataBinding/IPerspexPropertyBinder.cs
  11. 11
      src/Markup/Perspex.Markup.Xaml/DataBinding/PerspexPropertyBinder.cs
  12. 58
      src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs
  13. 3
      src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBindingDefinition.cs
  14. 4
      src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  15. 9
      src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
  16. 4
      src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs
  17. 48
      src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs
  18. 1
      src/Markup/Perspex.Markup/Perspex.Markup.csproj
  19. 1
      src/Perspex.Controls/Properties/AssemblyInfo.cs
  20. 3
      tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs
  21. 72
      tests/Perspex.Markup.Xaml.UnitTests/ChangeBranchTest.cs
  22. 103
      tests/Perspex.Markup.Xaml.UnitTests/DataContextChangeSynchronizerTest.cs
  23. 3
      tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj
  24. 18
      tests/Perspex.Markup.Xaml.UnitTests/PropertyMountPointTest.cs
  25. 6
      tests/Perspex.Markup.Xaml.UnitTests/XamlBindingTest.cs

2
samples/XamlTestApplication/Views/TestNode.cs → samples/XamlTestApplication/ViewModels/TestNode.cs

@ -11,4 +11,4 @@ namespace XamlTestApplication.ViewModels
public string SubHeader { get; set; }
public IEnumerable<TestNode> Children { get; set; }
}
}
}

6
samples/XamlTestApplication/Views/MainWindow.cs

@ -1,12 +1,6 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Resources;
using OmniXaml;
using Perspex.Controls;
using Perspex.Diagnostics;
using Perspex.Markup.Xaml;

2
samples/XamlTestApplication/XamlTestApplication.csproj

@ -80,8 +80,8 @@
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ViewModels\MainWindowViewModel.cs" />
<Compile Include="Views\TestNode.cs" />
<Compile Include="ViewModels\TestItem.cs" />
<Compile Include="ViewModels\TestNode.cs" />
<Compile Include="Views\MainWindow.cs" />
</ItemGroup>
<ItemGroup>

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

@ -54,22 +54,26 @@ namespace Perspex.Markup.Xaml.Context
po.SetValue(pp, value);
}
private void HandleXamlBindingDefinition(XamlBindingDefinition xamlBindingDefinition)
private void HandleXamlBindingDefinition(XamlBindingDefinition def)
{
PerspexObject subjectObject = xamlBindingDefinition.Target;
_propertyBinder.Create(xamlBindingDefinition);
var binding = new XamlBinding(_propertyBinder.TypeConverterProvider)
{
BindingMode = def.BindingMode,
SourcePropertyPath = def.SourcePropertyPath,
Target = def.Target,
TargetProperty = def.TargetProperty,
};
var observableForDataContext = subjectObject.GetObservable(Control.DataContextProperty);
observableForDataContext.Where(o => o != null).Subscribe(_ => BindToDataContextWhenItsSet(xamlBindingDefinition));
binding.Bind();
}
private void BindToDataContextWhenItsSet(XamlBindingDefinition definition)
{
var target = definition.Target;
var dataContext = target.DataContext;
// var target = definition.Target;
// var dataContext = target.DataContext;
var binding = _propertyBinder.GetBinding(target, definition.TargetProperty);
binding.BindToDataContext(dataContext);
// var binding = _propertyBinder.GetBinding(target, definition.TargetProperty);
// binding.BindToDataContext(dataContext);
}
// ReSharper disable once MemberCanBePrivate.Global

109
src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/ObservablePropertyBranch.cs

@ -1,109 +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.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
using Glass;
namespace Perspex.Markup.Xaml.DataBinding.ChangeTracking
{
public class ObservablePropertyBranch
{
private readonly object _instance;
private readonly PropertyPath _propertyPath;
private readonly PropertyMountPoint _mountPoint;
public ObservablePropertyBranch(object instance, PropertyPath propertyPath)
{
Guard.ThrowIfNull(instance, nameof(instance));
Guard.ThrowIfNull(propertyPath, nameof(propertyPath));
_instance = instance;
_propertyPath = propertyPath;
_mountPoint = new PropertyMountPoint(instance, propertyPath);
var properties = GetPropertiesThatRaiseNotifications();
Values = CreateUnifiedObservableFromNodes(properties);
}
public IObservable<object> Values { get; private set; }
private IObservable<object> CreateUnifiedObservableFromNodes(IEnumerable<PropertyDefinition> subscriptions)
{
return subscriptions.Select(GetObservableFromProperty).Merge();
}
private IObservable<object> GetObservableFromProperty(PropertyDefinition subscription)
{
return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
parentOnPropertyChanged => subscription.Parent.PropertyChanged += parentOnPropertyChanged,
parentOnPropertyChanged => subscription.Parent.PropertyChanged -= parentOnPropertyChanged)
.Where(pattern => pattern.EventArgs.PropertyName == subscription.PropertyName)
.Select(pattern => _mountPoint.Value);
}
private IEnumerable<PropertyDefinition> GetPropertiesThatRaiseNotifications()
{
return GetSubscriptionsRecursive(_instance, _propertyPath, 0);
}
private IEnumerable<PropertyDefinition> GetSubscriptionsRecursive(object current, PropertyPath propertyPath, int i)
{
var subscriptions = new List<PropertyDefinition>();
var inpc = current as INotifyPropertyChanged;
if (inpc == null)
{
return subscriptions;
}
var nextPropertyName = propertyPath.Chunks[i];
subscriptions.Add(new PropertyDefinition(inpc, nextPropertyName));
if (i < _propertyPath.Chunks.Length)
{
var currentObjectTypeInfo = current.GetType().GetTypeInfo();
var nextProperty = currentObjectTypeInfo.GetDeclaredProperty(nextPropertyName);
var nextInstance = nextProperty.GetValue(current);
if (i < _propertyPath.Chunks.Length - 1)
{
subscriptions.AddRange(GetSubscriptionsRecursive(nextInstance, propertyPath, i + 1));
}
}
return subscriptions;
}
public object Value
{
get
{
return _mountPoint.Value;
}
set
{
_mountPoint.Value = value;
}
}
public Type Type => _mountPoint.ProperyType;
private class PropertyDefinition
{
public PropertyDefinition(INotifyPropertyChanged parent, string propertyName)
{
Parent = parent;
PropertyName = propertyName;
}
public INotifyPropertyChanged Parent { get; }
public string PropertyName { get; }
}
}
}

52
src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyMountPoint.cs

@ -1,52 +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.Reflection;
using Glass;
namespace Perspex.Markup.Xaml.DataBinding.ChangeTracking
{
public class PropertyMountPoint
{
private readonly TargettedProperty _referencedTargettedProperty;
public PropertyMountPoint(object origin, PropertyPath propertyPath)
{
Guard.ThrowIfNull(origin, nameof(origin));
Guard.ThrowIfNull(propertyPath, nameof(propertyPath));
_referencedTargettedProperty = GetReferencedPropertyInfo(origin, propertyPath, 0);
}
private static TargettedProperty GetReferencedPropertyInfo(object current, PropertyPath propertyPath, int level)
{
var typeInfo = current.GetType().GetTypeInfo();
var leftPropertyInfo = typeInfo.GetDeclaredProperty(propertyPath.Chunks[level]);
if (level == propertyPath.Chunks.Length - 1)
{
return new TargettedProperty(current, leftPropertyInfo);
}
var nextInstance = leftPropertyInfo.GetValue(current);
return GetReferencedPropertyInfo(nextInstance, propertyPath, level + 1);
}
public object Value
{
get
{
return _referencedTargettedProperty.Value;
}
set
{
_referencedTargettedProperty.Value = value;
}
}
public Type ProperyType => _referencedTargettedProperty.PropertyType;
}
}

36
src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyPath.cs

@ -1,36 +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.
namespace Perspex.Markup.Xaml.DataBinding.ChangeTracking
{
public class PropertyPath
{
private string[] _chunks;
private PropertyPath(PropertyPath propertyPath)
{
_chunks = propertyPath.Chunks;
}
public PropertyPath(string path)
{
_chunks = path.Split('.');
}
public string[] Chunks
{
get { return _chunks; }
set { _chunks = value; }
}
public PropertyPath Clone()
{
return new PropertyPath(this);
}
public override string ToString()
{
return string.Join(".", _chunks);
}
}
}

41
src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/TargettedProperty.cs

@ -1,41 +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.Reflection;
using Glass;
namespace Perspex.Markup.Xaml.DataBinding.ChangeTracking
{
internal class TargettedProperty
{
private readonly object _instance;
private readonly PropertyInfo _propertyInfo;
public TargettedProperty(object instance, PropertyInfo propertyInfo)
{
Guard.ThrowIfNull(instance, nameof(instance));
Guard.ThrowIfNull(propertyInfo, nameof(propertyInfo));
_instance = instance;
_propertyInfo = propertyInfo;
}
public object Value
{
get
{
return _propertyInfo.GetValue(_instance);
}
set
{
_propertyInfo.SetValue(_instance, value);
}
}
public Type PropertyType => _propertyInfo.PropertyType;
public string Name => _propertyInfo.Name;
}
}

165
src/Markup/Perspex.Markup.Xaml/DataBinding/DataContextChangeSynchronizer.cs

@ -1,165 +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.Globalization;
using System.Reactive.Linq;
using System.Reflection;
using Glass;
using OmniXaml.TypeConversion;
using Perspex.Markup.Xaml.DataBinding.ChangeTracking;
namespace Perspex.Markup.Xaml.DataBinding
{
public class DataContextChangeSynchronizer
{
private readonly BindingTarget _bindingTarget;
private readonly ITypeConverter _targetPropertyTypeConverter;
private readonly TargetBindingEndpoint _bindingEndpoint;
private readonly ObservablePropertyBranch _sourceEndpoint;
public DataContextChangeSynchronizer(BindingSource bindingSource, BindingTarget bindingTarget, ITypeConverterProvider typeConverterProvider)
{
_bindingTarget = bindingTarget;
Guard.ThrowIfNull(bindingTarget.Object, nameof(bindingTarget.Object));
Guard.ThrowIfNull(bindingTarget.Property, nameof(bindingTarget.Property));
Guard.ThrowIfNull(bindingSource.SourcePropertyPath, nameof(bindingSource.SourcePropertyPath));
Guard.ThrowIfNull(bindingSource.Source, nameof(bindingSource.Source));
Guard.ThrowIfNull(typeConverterProvider, nameof(typeConverterProvider));
_bindingEndpoint = new TargetBindingEndpoint(bindingTarget.Object, bindingTarget.Property);
_sourceEndpoint = new ObservablePropertyBranch(bindingSource.Source, bindingSource.SourcePropertyPath);
_targetPropertyTypeConverter = typeConverterProvider.GetTypeConverter(bindingTarget.Property.PropertyType);
}
public class BindingTarget
{
private readonly PerspexObject _obj;
private readonly PerspexProperty _property;
public BindingTarget(PerspexObject @object, PerspexProperty property)
{
_obj = @object;
_property = property;
}
public PerspexObject Object => _obj;
public PerspexProperty Property => _property;
public object Value
{
get { return _obj.GetValue(_property); }
set { _obj.SetValue(_property, value); }
}
}
public class BindingSource
{
private readonly PropertyPath _sourcePropertyPath;
private readonly object _source;
public BindingSource(PropertyPath sourcePropertyPath, object source)
{
_sourcePropertyPath = sourcePropertyPath;
_source = source;
}
public PropertyPath SourcePropertyPath => _sourcePropertyPath;
public object Source => _source;
}
public void StartUpdatingTargetWhenSourceChanges()
{
// TODO: commenting out this line will make the existing value to be skipped from the SourceValues. This is not supposed to happen. Is it?
_bindingTarget.Value = ConvertedValue(_sourceEndpoint.Value, _bindingTarget.Property.PropertyType);
// We use the native Bind method from PerspexObject to subscribe to the SourceValues observable
_bindingTarget.Object.Bind(_bindingTarget.Property, SourceValues);
}
public void StartUpdatingSourceWhenTargetChanges()
{
// We subscribe to the TargetValues and each time we have a new value, we update the source with it
TargetValues.Subscribe(newValue => _sourceEndpoint.Value = newValue);
}
private IObservable<object> SourceValues
{
get
{
return _sourceEndpoint.Values.Select(originalValue => ConvertedValue(originalValue, _bindingTarget.Property.PropertyType));
}
}
private IObservable<object> TargetValues
{
get
{
return _bindingEndpoint.Object
.GetObservable(_bindingEndpoint.Property).Select(o => ConvertedValue(o, _sourceEndpoint.Type));
}
}
private bool CanAssignWithoutConversion
{
get
{
var sourceTypeInfo = _sourceEndpoint.Type.GetTypeInfo();
var targetTypeInfo = _bindingEndpoint.Property.PropertyType.GetTypeInfo();
var compatible = targetTypeInfo.IsAssignableFrom(sourceTypeInfo);
return compatible;
}
}
private object ConvertedValue(object originalValue, Type propertyType)
{
object converted;
if (TryConvert(originalValue, propertyType, out converted))
{
return converted;
}
return null;
}
private bool TryConvert(object originalValue, Type targetType, out object finalValue)
{
if (originalValue != null)
{
if (CanAssignWithoutConversion)
{
finalValue = originalValue;
return true;
}
if (_targetPropertyTypeConverter != null)
{
if (_targetPropertyTypeConverter.CanConvertTo(null, targetType))
{
object convertedValue = _targetPropertyTypeConverter.ConvertTo(
null,
CultureInfo.InvariantCulture,
originalValue,
targetType);
if (convertedValue != null)
{
finalValue = convertedValue;
return true;
}
}
}
}
else
{
finalValue = null;
return true;
}
finalValue = null;
return false;
}
}
}

3
src/Markup/Perspex.Markup.Xaml/DataBinding/IPerspexPropertyBinder.cs

@ -2,11 +2,14 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using OmniXaml.TypeConversion;
namespace Perspex.Markup.Xaml.DataBinding
{
public interface IPerspexPropertyBinder
{
ITypeConverterProvider TypeConverterProvider { get; }
XamlBinding GetBinding(PerspexObject po, PerspexProperty pp);
IEnumerable<XamlBinding> GetBindings(PerspexObject source);

11
src/Markup/Perspex.Markup.Xaml/DataBinding/PerspexPropertyBinder.cs

@ -5,22 +5,21 @@ using System;
using System.Collections.Generic;
using System.Linq;
using OmniXaml.TypeConversion;
using Perspex.Markup.Xaml.DataBinding.ChangeTracking;
namespace Perspex.Markup.Xaml.DataBinding
{
public class PerspexPropertyBinder : IPerspexPropertyBinder
{
private readonly ITypeConverterProvider _typeConverterProvider;
private readonly HashSet<XamlBinding> _bindings;
public PerspexPropertyBinder(ITypeConverterProvider typeConverterProvider)
{
_typeConverterProvider = typeConverterProvider;
TypeConverterProvider = typeConverterProvider;
_bindings = new HashSet<XamlBinding>();
}
public ITypeConverterProvider TypeConverterProvider { get; }
public XamlBinding GetBinding(PerspexObject po, PerspexProperty pp)
{
return _bindings.First(xamlBinding => xamlBinding.Target == po && xamlBinding.TargetProperty == pp);
@ -45,10 +44,10 @@ namespace Perspex.Markup.Xaml.DataBinding
throw new InvalidOperationException();
}
var binding = new XamlBinding(_typeConverterProvider)
var binding = new XamlBinding(TypeConverterProvider)
{
BindingMode = xamlBinding.BindingMode,
SourcePropertyPath = new PropertyPath(xamlBinding.SourcePropertyPath),
SourcePropertyPath = xamlBinding.SourcePropertyPath,
Target = xamlBinding.Target,
TargetProperty = xamlBinding.TargetProperty
};

58
src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs

@ -2,16 +2,15 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Diagnostics;
using System.Reactive.Linq;
using OmniXaml.TypeConversion;
using Perspex.Markup.Xaml.DataBinding.ChangeTracking;
using Perspex.Markup.Binding;
namespace Perspex.Markup.Xaml.DataBinding
{
public class XamlBinding
{
private readonly ITypeConverterProvider _typeConverterProvider;
private DataContextChangeSynchronizer _changeSynchronizer;
public XamlBinding(ITypeConverterProvider typeConverterProvider)
{
@ -22,44 +21,43 @@ namespace Perspex.Markup.Xaml.DataBinding
public PerspexProperty TargetProperty { get; set; }
public PropertyPath SourcePropertyPath { get; set; }
public object Source { get; set; }
public string SourcePropertyPath { get; set; }
public BindingMode BindingMode { get; set; }
public void BindToDataContext(object dataContext)
public void Bind()
{
if (dataContext == null)
{
return;
}
var path = SourcePropertyPath;
var source = Source;
try
if (source == null)
{
var bindingSource = new DataContextChangeSynchronizer.BindingSource(SourcePropertyPath, dataContext);
var bindingTarget = new DataContextChangeSynchronizer.BindingTarget(Target, TargetProperty);
var mode = BindingMode == BindingMode.Default ? TargetProperty.DefaultBindingMode : BindingMode;
_changeSynchronizer = new DataContextChangeSynchronizer(bindingSource, bindingTarget, _typeConverterProvider);
if (mode == BindingMode.TwoWay)
{
_changeSynchronizer.StartUpdatingTargetWhenSourceChanges();
_changeSynchronizer.StartUpdatingSourceWhenTargetChanges();
}
if (mode == BindingMode.OneWay || mode == BindingMode.Default)
if (!string.IsNullOrWhiteSpace(path))
{
_changeSynchronizer.StartUpdatingTargetWhenSourceChanges();
path = "DataContext." + path;
}
if (mode == BindingMode.OneWayToSource)
{
_changeSynchronizer.StartUpdatingSourceWhenTargetChanges();
}
source = Target;
}
catch (Exception e)
var observable = new ExpressionObserver(source, path);
var mode = BindingMode == BindingMode.Default ?
TargetProperty.DefaultBindingMode : BindingMode;
switch (mode)
{
Debug.WriteLine(e);
case BindingMode.Default:
case BindingMode.OneWay:
Target.Bind(TargetProperty, observable.Select(x => x.Value));
break;
case BindingMode.TwoWay:
throw new NotImplementedException();
case BindingMode.OneTime:
throw new NotImplementedException();
case BindingMode.OneWayToSource:
throw new NotImplementedException();
}
}
}

3
src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBindingDefinition.cs

@ -20,11 +20,8 @@ namespace Perspex.Markup.Xaml.DataBinding
}
public Control Target { get; }
public PerspexProperty TargetProperty { get; }
public string SourcePropertyPath { get; }
public BindingMode BindingMode { get; }
}
}

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

@ -5,7 +5,6 @@ using System.Linq;
using OmniXaml;
using Perspex.Controls;
using Perspex.Markup.Xaml.DataBinding;
using Perspex.Markup.Xaml.DataBinding.ChangeTracking;
namespace Perspex.Markup.Xaml.MarkupExtensions
{
@ -26,13 +25,10 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
var targetProperty = extensionContext.TargetProperty;
var targetPropertyName = targetProperty.Name;
var perspexProperty = target.GetRegisteredProperties().First(property => property.Name == targetPropertyName);
return new XamlBindingDefinition(target, perspexProperty, Path, Mode);
}
/// <summary> The source path (for CLR bindings).</summary>
public string Path { get; set; }
public BindingMode Mode { get; set; }
}
}

9
src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj

@ -54,10 +54,6 @@
<Compile Include="Converters\BrushTypeConverter.cs" />
<Compile Include="Converters\BitmapTypeConverter.cs" />
<Compile Include="Converters\GridLengthTypeConverter.cs" />
<Compile Include="DataBinding\ChangeTracking\ObservablePropertyBranch.cs" />
<Compile Include="DataBinding\ChangeTracking\PropertyMountPoint.cs" />
<Compile Include="DataBinding\ChangeTracking\PropertyPath.cs" />
<Compile Include="DataBinding\ChangeTracking\TargettedProperty.cs" />
<Compile Include="Context\PerspexParserFactory.cs" />
<Compile Include="OmniXAML\Source\Glass\AutoKeyDictionary.cs" />
<Compile Include="OmniXAML\Source\Glass\ChangeTracking\ObservableProperty.cs" />
@ -228,7 +224,6 @@
<Compile Include="Parsers\SelectorParser.cs" />
<Compile Include="Parsers\SelectorGrammar.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="DataBinding\DataContextChangeSynchronizer.cs" />
<Compile Include="DataBinding\IPerspexPropertyBinder.cs" />
<Compile Include="DataBinding\PerspexPropertyBinder.cs" />
<Compile Include="DataBinding\SourceBindingEndpoint.cs" />
@ -290,6 +285,10 @@
<Project>{F1BAA01A-F176-4C6A-B39D-5B40BB1B148F}</Project>
<Name>Perspex.Styling</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.Markup\Perspex.Markup.csproj">
<Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project>
<Name>Perspex.Markup</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Reference Include="Serilog, Version=1.5.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10">

4
src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs

@ -2,15 +2,13 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
namespace Perspex.Markup.Binding
{
/// <summary>
/// Observes the value of an expression on a root object.
/// Observes and sets the value of an expression on an object.
/// </summary>
public class ExpressionObserver : ObservableBase<ExpressionValue>
{

48
src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs

@ -0,0 +1,48 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Linq;
using System.Reactive.Subjects;
namespace Perspex.Markup.Binding
{
/// <summary>
/// Turns an <see cref="ExpressionObserver"/> into a subject that can be bound two-ways.
/// </summary>
public class ExpressionSubject : ISubject<object>
{
private ExpressionObserver _inner;
/// <summary>
/// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
/// </summary>
/// <param name="inner">The <see cref="ExpressionObserver"/>.</param>
public ExpressionSubject(ExpressionObserver inner)
{
_inner = inner;
}
/// <inheritdoc/>
public void OnCompleted()
{
}
/// <inheritdoc/>
public void OnError(Exception error)
{
}
/// <inheritdoc/>
public void OnNext(object value)
{
_inner.SetValue(value);
}
/// <inheritdoc/>
public IDisposable Subscribe(IObserver<object> observer)
{
return _inner.Select(x => x.Value).Distinct().Subscribe(observer);
}
}
}

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

@ -36,6 +36,7 @@
<ItemGroup>
<Compile Include="Binding\ExpressionNodeBuilder.cs" />
<Compile Include="Binding\ExpressionParseException.cs" />
<Compile Include="Binding\ExpressionSubject.cs" />
<Compile Include="Binding\ExpressionValue.cs" />
<Compile Include="Binding\LogicalNotNode.cs" />
<Compile Include="Binding\IndexerNode.cs" />

1
src/Perspex.Controls/Properties/AssemblyInfo.cs

@ -9,5 +9,6 @@ using Perspex.Metadata;
[assembly: InternalsVisibleTo("Perspex.Controls.UnitTests")]
[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls")]
[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls.Presenters")]
[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls.Primitives")]
[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls.Shapes")]

3
tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs

@ -11,7 +11,6 @@ namespace Perspex.Xaml.Base.UnitTest
private readonly BindingMode _bindingMode;
private readonly string _sourcePropertyPath;
private Control _target;
private PerspexProperty _targetProperty;
public BindingDefinitionBuilder()
{
@ -31,7 +30,7 @@ namespace Perspex.Xaml.Base.UnitTest
bindingMode: _bindingMode,
sourcePropertyPath: _sourcePropertyPath,
target: _target,
targetProperty: _targetProperty);
targetProperty: null);
}
}
}

72
tests/Perspex.Markup.Xaml.UnitTests/ChangeBranchTest.cs

@ -1,72 +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 Perspex.Markup.Xaml.DataBinding.ChangeTracking;
using Perspex.Xaml.Base.UnitTest.SampleModel;
using Xunit;
namespace Perspex.Xaml.Base.UnitTest
{
public class ChangeBranchTest
{
[Fact]
public void GetValueOfMemberOfStruct()
{
var level1 = new Level1();
level1.DateTime = new DateTime(1, 2, 3, 4, 5, 6);
var branch = new ObservablePropertyBranch(level1, new PropertyPath("DateTime.Minute"));
var day = branch.Value;
Assert.Equal(day, branch.Value);
}
[Fact]
public void OnePathOnly()
{
var level1 = new Level1();
var branch = new ObservablePropertyBranch(level1, new PropertyPath("Text"));
var newValue = "Hey now";
branch.Value = newValue;
Assert.Equal(level1.Text, newValue);
}
[Fact]
public void SettingValueToUnderlyingProperty_ChangesTheValueInBranch()
{
var level1 = new Level1();
level1.Level2.Level3.Property = 3;
var branch = new ObservablePropertyBranch(level1, new PropertyPath("Level2.Level3.Property"));
Assert.Equal(3, branch.Value);
}
[Fact]
public void SettingValueToBranch_ChangesTheUnderlyingProperty()
{
var level1 = new Level1();
var branch = new ObservablePropertyBranch(level1, new PropertyPath("Level2.Level3.Property"));
branch.Value = 3;
Assert.Equal(3, level1.Level2.Level3.Property);
}
[Fact]
public void SettingValueProperty_RaisesChangeInBranch()
{
var level1 = new Level1();
var branch = new ObservablePropertyBranch(level1, new PropertyPath("Level2.Level3.Property"));
bool received = false;
ObservableExtensions.Subscribe(branch.Values, v => received = ((int)v == 3));
level1.Level2.Level3.Property = 3;
Assert.True(received);
}
}
}

103
tests/Perspex.Markup.Xaml.UnitTests/DataContextChangeSynchronizerTest.cs

@ -1,103 +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 Perspex.Controls;
using GitHubClient.ViewModels;
using Perspex.Markup.Xaml.DataBinding;
using Perspex.Markup.Xaml.DataBinding.ChangeTracking;
using OmniXaml.Builder;
using OmniXaml.TypeConversion;
using OmniXaml.TypeConversion.BuiltInConverters;
using Perspex.Xaml.Base.UnitTest.SampleModel;
using Xunit;
namespace Perspex.Xaml.Base.UnitTest
{
public class DataContextChangeSynchronizerTest
{
private readonly TypeConverterProvider _repo;
private readonly SamplePerspexObject _guiObject;
private readonly ViewModelMock _viewModel;
public DataContextChangeSynchronizerTest()
{
_repo = new TypeConverterProvider();
_guiObject = new SamplePerspexObject();
_viewModel = new ViewModelMock();
}
[Fact]
public void SameTypesFromUIToModel()
{
var synchronizer = new DataContextChangeSynchronizer(new DataContextChangeSynchronizer.BindingSource(new PropertyPath("IntProp"), _viewModel), new DataContextChangeSynchronizer.BindingTarget(_guiObject, SamplePerspexObject.IntProperty), _repo);
synchronizer.StartUpdatingSourceWhenTargetChanges();
const int someValue = 4;
_guiObject.Int = someValue;
Assert.Equal(someValue, _viewModel.IntProp);
}
[Fact]
public void DifferentTypesFromUIToModel()
{
var synchronizer = new DataContextChangeSynchronizer(new DataContextChangeSynchronizer.BindingSource(new PropertyPath("IntProp"), _viewModel), new DataContextChangeSynchronizer.BindingTarget(_guiObject, SamplePerspexObject.StringProperty), _repo);
synchronizer.StartUpdatingSourceWhenTargetChanges();
_guiObject.String = "2";
Assert.Equal(2, _viewModel.IntProp);
}
[Fact]
public void DifferentTypesAndNonConvertibleValueFromUIToModel()
{
var synchronizer = new DataContextChangeSynchronizer(new DataContextChangeSynchronizer.BindingSource(new PropertyPath("IntProp"), _viewModel), new DataContextChangeSynchronizer.BindingTarget(_guiObject, SamplePerspexObject.StringProperty), _repo);
synchronizer.StartUpdatingSourceWhenTargetChanges();
_guiObject.String = "";
Assert.Equal(default(int), _viewModel.IntProp);
}
[Fact]
public void DifferentTypesFromModelToUI()
{
var synchronizer = new DataContextChangeSynchronizer(new DataContextChangeSynchronizer.BindingSource(new PropertyPath("IntProp"), _viewModel), new DataContextChangeSynchronizer.BindingTarget(_guiObject, SamplePerspexObject.StringProperty), _repo);
synchronizer.StartUpdatingTargetWhenSourceChanges();
_viewModel.IntProp = 2;
Assert.Equal("2", _guiObject.String);
}
[Fact]
public void SameTypesFromModelToUI()
{
var synchronizer = new DataContextChangeSynchronizer(new DataContextChangeSynchronizer.BindingSource(new PropertyPath("IntProp"), _viewModel), new DataContextChangeSynchronizer.BindingTarget(_guiObject, SamplePerspexObject.IntProperty), _repo);
synchronizer.StartUpdatingTargetWhenSourceChanges();
_viewModel.IntProp = 2;
Assert.Equal(2, _guiObject.Int);
}
[Fact]
public void GrokysTest()
{
var mainWindowViewModel = new MainWindowViewModel();
var contentControl = new ContentControl();
var synchronizer = new DataContextChangeSynchronizer(new DataContextChangeSynchronizer.BindingSource(new PropertyPath("Content"), mainWindowViewModel), new DataContextChangeSynchronizer.BindingTarget(contentControl, ContentControl.ContentProperty), _repo);
synchronizer.StartUpdatingTargetWhenSourceChanges();
var logInViewModel = new LogInViewModel();
mainWindowViewModel.Content = logInViewModel;
Assert.Equal(logInViewModel, contentControl.Content);
}
}
}

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

@ -88,9 +88,7 @@
</Choose>
<ItemGroup>
<Compile Include="BindingDefinitionBuilder.cs" />
<Compile Include="ChangeBranchTest.cs" />
<Compile Include="Converters\PerspexPropertyConverterTest.cs" />
<Compile Include="DataContextChangeSynchronizerTest.cs" />
<Compile Include="Parsers\SelectorGrammarTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SampleModel\Level1.cs" />
@ -105,7 +103,6 @@
<Compile Include="TypeProviderMock.cs" />
<Compile Include="BinderTest.cs" />
<Compile Include="XamlBindingTest.cs" />
<Compile Include="PropertyMountPointTest.cs" />
<Compile Include="ViewModelMock.cs" />
</ItemGroup>
<ItemGroup>

18
tests/Perspex.Markup.Xaml.UnitTests/PropertyMountPointTest.cs

@ -1,18 +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 Perspex.Markup.Xaml.DataBinding.ChangeTracking;
using System;
using Xunit;
namespace Perspex.Xaml.Base.UnitTest
{
public class PropertyMountPointTest
{
[Fact]
public void SourceAndPathAreNull()
{
Assert.Throws<ArgumentNullException>(() => new PropertyMountPoint(null, null));
}
}
}

6
tests/Perspex.Markup.Xaml.UnitTests/XamlBindingTest.cs

@ -13,9 +13,9 @@ namespace Perspex.Xaml.Base.UnitTest
[Fact]
public void TestNullDataContext()
{
var t = new Mock<ITypeConverterProvider>();
var sut = new XamlBinding(t.Object);
sut.BindToDataContext(null);
//var t = new Mock<ITypeConverterProvider>();
//var sut = new XamlBinding(t.Object);
//sut.BindTo(null);
}
}
}

Loading…
Cancel
Save