Browse Source

Basic binding using ExpressionObserver.

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

52
samples/XamlTestApplication/MainViewModel.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.Collections.Generic;
using ReactiveUI;
namespace XamlTestApplication
{
public class MainViewModel : ReactiveObject
{
private string _name;
public MainViewModel()
{
Name = "Jos\u00E9 Manuel";
People = new List<Person>
{
new Person("a little bit of Monica in my life"),
new Person("a little bit of Erica by my side"),
new Person("a little bit of Rita is all I need"),
new Person("a little bit of Tina is what I see"),
new Person("a little bit of Sandra in the sun"),
new Person("a little bit of Mary all night long"),
new Person("a little bit of Jessica here I am"),
};
}
public string Name
{
get { return _name; }
set { this.RaiseAndSetIfChanged(ref _name, value); }
}
public List<Person> People { get; set; }
}
public class Person
{
private string _name;
public Person(string name)
{
_name = name;
}
public string Name
{
get { return _name; }
set { _name = value; }
}
}
}

58
samples/XamlTestApplication/ViewModels/MainWindowViewModel.cs

@ -0,0 +1,58 @@
// 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.Collections.Generic;
namespace XamlTestApplication.ViewModels
{
public class MainWindowViewModel
{
public MainWindowViewModel()
{
Items = new List<TestItem>();
for (int i = 0; i < 10; ++i)
{
Items.Add(new TestItem($"Item {i}", $"Item {i} Value"));
}
Nodes = new List<TestNode>
{
new TestNode
{
Header = "Root",
SubHeader = "Root Item",
Children = new[]
{
new TestNode
{
Header = "Child 1",
SubHeader = "Child 1 Value",
},
new TestNode
{
Header = "Child 2",
SubHeader = "Child 2 Value",
Children = new[]
{
new TestNode
{
Header = "Grandchild",
SubHeader = "Grandchild Value",
},
new TestNode
{
Header = "Grandmaster Flash",
SubHeader = "White Lines",
},
}
},
}
}
};
}
public List<TestItem> Items { get; }
public List<TestNode> Nodes { get; }
}
}

17
samples/XamlTestApplication/ViewModels/TestItem.cs

@ -0,0 +1,17 @@
// 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 XamlTestApplication.ViewModels
{
public class TestItem
{
public TestItem(string header, string subheader)
{
Header = header;
SubHeader = subheader;
}
public string Header { get; }
public string SubHeader { get; }
}
}

14
samples/XamlTestApplication/ViewModels/TestNode.cs

@ -0,0 +1,14 @@
// 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.Collections.Generic;
namespace XamlTestApplication.ViewModels
{
public class TestNode
{
public string Header { get; set; }
public string SubHeader { get; set; }
public IEnumerable<TestNode> Children { get; set; }
}
}

9
samples/XamlTestApplication/Views/MainWindow.cs

@ -1,15 +1,10 @@
// 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;
using XamlTestApplication.ViewModels;
namespace XamlTestApplication.Views
{
@ -18,7 +13,7 @@ namespace XamlTestApplication.Views
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
DevTools.Attach(this);
}

16
samples/XamlTestApplication/Views/MainWindow.paml

@ -48,20 +48,8 @@
</TabItem>
<TabItem Header="Lists">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
<ListBox SelectedIndex="0">
<ListBoxItem>
<StackPanel>
<TextBlock Text="Item 1" FontSize="24" />
<TextBlock Text="Item 1 Value" />
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel>
<TextBlock Text="Item 2" FontSize="24" />
<TextBlock Text="Item 2 Value" />
</StackPanel>
</ListBoxItem>
</ListBox>
<ListBox Items="{Binding Items}">
</ListBox>
<DropDown VerticalAlignment="Center" SelectedIndex="0">
<StackPanel>
<TextBlock Text="Item 1" FontSize="24" />

4
samples/XamlTestApplication/XamlTestApplication.csproj

@ -77,9 +77,11 @@
</ItemGroup>
<ItemGroup>
<Compile Include="App.cs" />
<Compile Include="MainViewModel.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ViewModels\MainWindowViewModel.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);

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

@ -10,16 +10,16 @@ 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);
@ -44,7 +44,7 @@ namespace Perspex.Markup.Xaml.DataBinding
throw new InvalidOperationException();
}
var binding = new XamlBinding(_typeConverterProvider)
var binding = new XamlBinding(TypeConverterProvider)
{
BindingMode = xamlBinding.BindingMode,
SourcePropertyPath = xamlBinding.SourcePropertyPath,

57
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,43 +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);
_changeSynchronizer = new DataContextChangeSynchronizer(bindingSource, bindingTarget, _typeConverterProvider);
if (BindingMode == BindingMode.TwoWay)
{
_changeSynchronizer.StartUpdatingTargetWhenSourceChanges();
_changeSynchronizer.StartUpdatingSourceWhenTargetChanges();
}
if (BindingMode == BindingMode.OneWay)
if (!string.IsNullOrWhiteSpace(path))
{
_changeSynchronizer.StartUpdatingTargetWhenSourceChanges();
path = "DataContext." + path;
}
if (BindingMode == 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();
}
}
}

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

@ -2,31 +2,26 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Perspex.Controls;
using Perspex.Markup.Xaml.DataBinding.ChangeTracking;
namespace Perspex.Markup.Xaml.DataBinding
{
public class XamlBindingDefinition
{
private readonly PropertyPath _sourcePropertyPath;
private readonly BindingMode _bindingMode;
private readonly Control _target;
private readonly PerspexProperty _targetProperty;
public XamlBindingDefinition(Control target, PerspexProperty targetProperty, PropertyPath sourcePropertyPath, BindingMode bindingMode)
public XamlBindingDefinition(
Control target,
PerspexProperty targetProperty,
string sourcePropertyPath,
BindingMode bindingMode)
{
_target = target;
_targetProperty = targetProperty;
_sourcePropertyPath = sourcePropertyPath;
_bindingMode = bindingMode;
Target = target;
TargetProperty = targetProperty;
SourcePropertyPath = sourcePropertyPath;
BindingMode = bindingMode;
}
public Control Target => _target;
public PerspexProperty TargetProperty => _targetProperty;
public PropertyPath SourcePropertyPath => _sourcePropertyPath;
public BindingMode BindingMode => _bindingMode;
public Control Target { get; }
public PerspexProperty TargetProperty { get; }
public string SourcePropertyPath { get; }
public BindingMode BindingMode { get; }
}
}

12
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,19 +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,
new PropertyPath(Path),
Mode == BindingMode.Default ? BindingMode.OneWay : Mode
);
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" />
@ -289,6 +284,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">

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")]

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

@ -4,21 +4,19 @@
using System;
using Perspex.Controls;
using Perspex.Markup.Xaml.DataBinding;
using Perspex.Markup.Xaml.DataBinding.ChangeTracking;
namespace Perspex.Xaml.Base.UnitTest
{
public class BindingDefinitionBuilder
{
private readonly BindingMode _bindingMode;
private readonly PropertyPath _sourcePropertyPath;
private readonly string _sourcePropertyPath;
private Control _target;
private PerspexProperty _targetProperty;
public BindingDefinitionBuilder()
{
_bindingMode = BindingMode.Default;
_sourcePropertyPath = new PropertyPath(string.Empty);
_sourcePropertyPath = string.Empty;
}
public BindingDefinitionBuilder WithNullTarget()
@ -33,7 +31,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