diff --git a/samples/XamlTestApplication/Views/TestNode.cs b/samples/XamlTestApplication/ViewModels/TestNode.cs similarity index 99% rename from samples/XamlTestApplication/Views/TestNode.cs rename to samples/XamlTestApplication/ViewModels/TestNode.cs index 94c8fb6052..953bfd0f58 100644 --- a/samples/XamlTestApplication/Views/TestNode.cs +++ b/samples/XamlTestApplication/ViewModels/TestNode.cs @@ -11,4 +11,4 @@ namespace XamlTestApplication.ViewModels public string SubHeader { get; set; } public IEnumerable Children { get; set; } } -} +} \ No newline at end of file diff --git a/samples/XamlTestApplication/Views/MainWindow.cs b/samples/XamlTestApplication/Views/MainWindow.cs index fe2dd3d347..d18985a898 100644 --- a/samples/XamlTestApplication/Views/MainWindow.cs +++ b/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; diff --git a/samples/XamlTestApplication/XamlTestApplication.csproj b/samples/XamlTestApplication/XamlTestApplication.csproj index 28a5382d7e..107df97bcc 100644 --- a/samples/XamlTestApplication/XamlTestApplication.csproj +++ b/samples/XamlTestApplication/XamlTestApplication.csproj @@ -80,8 +80,8 @@ - + diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs index 423ca66e23..f978ecaa69 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs +++ b/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 diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/ObservablePropertyBranch.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/ObservablePropertyBranch.cs deleted file mode 100644 index 2756065280..0000000000 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/ObservablePropertyBranch.cs +++ /dev/null @@ -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 Values { get; private set; } - - private IObservable CreateUnifiedObservableFromNodes(IEnumerable subscriptions) - { - return subscriptions.Select(GetObservableFromProperty).Merge(); - } - - private IObservable GetObservableFromProperty(PropertyDefinition subscription) - { - return Observable.FromEventPattern( - parentOnPropertyChanged => subscription.Parent.PropertyChanged += parentOnPropertyChanged, - parentOnPropertyChanged => subscription.Parent.PropertyChanged -= parentOnPropertyChanged) - .Where(pattern => pattern.EventArgs.PropertyName == subscription.PropertyName) - .Select(pattern => _mountPoint.Value); - } - - private IEnumerable GetPropertiesThatRaiseNotifications() - { - return GetSubscriptionsRecursive(_instance, _propertyPath, 0); - } - - private IEnumerable GetSubscriptionsRecursive(object current, PropertyPath propertyPath, int i) - { - var subscriptions = new List(); - 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; } - } - } -} \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyMountPoint.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyMountPoint.cs deleted file mode 100644 index 8ea903fa55..0000000000 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyMountPoint.cs +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyPath.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyPath.cs deleted file mode 100644 index 3558645b1b..0000000000 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyPath.cs +++ /dev/null @@ -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); - } - } -} \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/TargettedProperty.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/TargettedProperty.cs deleted file mode 100644 index fec5edc904..0000000000 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/TargettedProperty.cs +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/DataContextChangeSynchronizer.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/DataContextChangeSynchronizer.cs deleted file mode 100644 index 27723b5f75..0000000000 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/DataContextChangeSynchronizer.cs +++ /dev/null @@ -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 SourceValues - { - get - { - return _sourceEndpoint.Values.Select(originalValue => ConvertedValue(originalValue, _bindingTarget.Property.PropertyType)); - } - } - - private IObservable 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; - } - } -} \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/IPerspexPropertyBinder.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/IPerspexPropertyBinder.cs index d7879dc29b..40d349e00d 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/IPerspexPropertyBinder.cs +++ b/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 GetBindings(PerspexObject source); diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/PerspexPropertyBinder.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/PerspexPropertyBinder.cs index 9b53ef52c7..916e3edd85 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/PerspexPropertyBinder.cs +++ b/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 _bindings; public PerspexPropertyBinder(ITypeConverterProvider typeConverterProvider) { - _typeConverterProvider = typeConverterProvider; + TypeConverterProvider = typeConverterProvider; _bindings = new HashSet(); } + 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 }; diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs index 837413a26f..c23a5bce22 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs +++ b/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(); } } } diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBindingDefinition.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBindingDefinition.cs index 62ebcf39ca..4cbd3bfe1e 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBindingDefinition.cs +++ b/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; } } } \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs index 593d245dba..55f2b1ee4a 100644 --- a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs +++ b/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); } - /// The source path (for CLR bindings). public string Path { get; set; } - public BindingMode Mode { get; set; } } } \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj index f0533e2688..a117474005 100644 --- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj +++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj @@ -54,10 +54,6 @@ - - - - @@ -228,7 +224,6 @@ - @@ -290,6 +285,10 @@ {F1BAA01A-F176-4C6A-B39D-5B40BB1B148F} Perspex.Styling + + {6417e941-21bc-467b-a771-0de389353ce6} + Perspex.Markup + diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs b/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs index bd0b08b7c3..c50868b549 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs +++ b/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 { /// - /// Observes the value of an expression on a root object. + /// Observes and sets the value of an expression on an object. /// public class ExpressionObserver : ObservableBase { diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs b/src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs new file mode 100644 index 0000000000..e2e94daf7c --- /dev/null +++ b/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 +{ + /// + /// Turns an into a subject that can be bound two-ways. + /// + public class ExpressionSubject : ISubject + { + private ExpressionObserver _inner; + + /// + /// Initializes a new instance of the class. + /// + /// The . + public ExpressionSubject(ExpressionObserver inner) + { + _inner = inner; + } + + /// + public void OnCompleted() + { + } + + /// + public void OnError(Exception error) + { + } + + /// + public void OnNext(object value) + { + _inner.SetValue(value); + } + + /// + public IDisposable Subscribe(IObserver observer) + { + return _inner.Select(x => x.Value).Distinct().Subscribe(observer); + } + } +} diff --git a/src/Markup/Perspex.Markup/Perspex.Markup.csproj b/src/Markup/Perspex.Markup/Perspex.Markup.csproj index aee9048774..d22201ee9f 100644 --- a/src/Markup/Perspex.Markup/Perspex.Markup.csproj +++ b/src/Markup/Perspex.Markup/Perspex.Markup.csproj @@ -36,6 +36,7 @@ + diff --git a/src/Perspex.Controls/Properties/AssemblyInfo.cs b/src/Perspex.Controls/Properties/AssemblyInfo.cs index bf1ddab5d3..7c0a189889 100644 --- a/src/Perspex.Controls/Properties/AssemblyInfo.cs +++ b/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")] \ No newline at end of file diff --git a/tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs b/tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs index 066a1ea12b..985850e3e9 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs +++ b/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); } } } \ No newline at end of file diff --git a/tests/Perspex.Markup.Xaml.UnitTests/ChangeBranchTest.cs b/tests/Perspex.Markup.Xaml.UnitTests/ChangeBranchTest.cs deleted file mode 100644 index 202cfed999..0000000000 --- a/tests/Perspex.Markup.Xaml.UnitTests/ChangeBranchTest.cs +++ /dev/null @@ -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); - } - } -} diff --git a/tests/Perspex.Markup.Xaml.UnitTests/DataContextChangeSynchronizerTest.cs b/tests/Perspex.Markup.Xaml.UnitTests/DataContextChangeSynchronizerTest.cs deleted file mode 100644 index 0b93a2421d..0000000000 --- a/tests/Perspex.Markup.Xaml.UnitTests/DataContextChangeSynchronizerTest.cs +++ /dev/null @@ -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); - } - } -} diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj b/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj index 3d489dd682..b74704c7fa 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj +++ b/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj @@ -88,9 +88,7 @@ - - @@ -105,7 +103,6 @@ - diff --git a/tests/Perspex.Markup.Xaml.UnitTests/PropertyMountPointTest.cs b/tests/Perspex.Markup.Xaml.UnitTests/PropertyMountPointTest.cs deleted file mode 100644 index 1b15094af5..0000000000 --- a/tests/Perspex.Markup.Xaml.UnitTests/PropertyMountPointTest.cs +++ /dev/null @@ -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(() => new PropertyMountPoint(null, null)); - } - } -} diff --git a/tests/Perspex.Markup.Xaml.UnitTests/XamlBindingTest.cs b/tests/Perspex.Markup.Xaml.UnitTests/XamlBindingTest.cs index d7ee56245f..fcd90f6333 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/XamlBindingTest.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/XamlBindingTest.cs @@ -13,9 +13,9 @@ namespace Perspex.Xaml.Base.UnitTest [Fact] public void TestNullDataContext() { - var t = new Mock(); - var sut = new XamlBinding(t.Object); - sut.BindToDataContext(null); + //var t = new Mock(); + //var sut = new XamlBinding(t.Object); + //sut.BindTo(null); } } }