diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 552bd48845..75b5b9da29 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -44,6 +44,7 @@ Properties\SharedAssemblyInfo.cs + diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index fd4f495edb..e6b88c71f8 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -178,59 +178,22 @@ namespace Avalonia /// Gets or sets a binding for a . /// /// The binding information. - public IObservable this[IndexerDescriptor binding] + public IBinding this[IndexerDescriptor binding] { get { - return CreateBindingDescriptor(binding); + return new IndexerBinding(this, binding.Property, binding.Mode); } set { var metadata = binding.Property.GetMetadata(GetType()); + var sourceBinding = value as IBinding; - var mode = (binding.Mode == BindingMode.Default) ? - metadata.DefaultBindingMode : - binding.Mode; - var sourceBinding = value as IndexerDescriptor; - - if (sourceBinding == null && mode > BindingMode.OneWay) - { - mode = BindingMode.OneWay; - } - - switch (mode) - { - case BindingMode.Default: - case BindingMode.OneWay: - Bind(binding.Property, value, binding.Priority); - break; - case BindingMode.OneTime: - SetValue(binding.Property, sourceBinding.Source.GetValue(sourceBinding.Property), binding.Priority); - break; - case BindingMode.OneWayToSource: - sourceBinding.Source.Bind(sourceBinding.Property, this.GetObservable(binding.Property), binding.Priority); - break; - case BindingMode.TwoWay: - var subject = sourceBinding.Source.GetSubject(sourceBinding.Property, sourceBinding.Priority); - var instanced = new InstancedBinding(subject, BindingMode.TwoWay, sourceBinding.Priority); - BindingOperations.Apply(this, binding.Property, instanced, null); - break; - } + this.Bind(binding.Property, sourceBinding); } } - protected virtual IndexerDescriptor CreateBindingDescriptor(IndexerDescriptor source) - { - return new IndexerDescriptor - { - Mode = source.Mode, - Priority = source.Priority, - Property = source.Property, - Source = this, - }; - } - public bool CheckAccess() => Dispatcher.UIThread.CheckAccess(); public void VerifyAccess() => Dispatcher.UIThread.VerifyAccess(); @@ -389,6 +352,8 @@ namespace Avalonia BindingPriority priority = BindingPriority.LocalValue) { Contract.Requires(property != null); + Contract.Requires(source != null); + VerifyAccess(); if (property.IsDirect) diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index e172e72f83..2751d8d5d5 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -16,6 +16,11 @@ namespace Avalonia /// public static class AvaloniaObjectExtensions { + public static IBinding AsBinding(this IObservable source) + { + return new BindingAdaptor(source.Select(x => (object)x)); + } + /// /// Gets an observable for a . /// @@ -293,5 +298,23 @@ namespace Avalonia handler(target)(e); } } + + private class BindingAdaptor : IBinding + { + private IObservable _source; + + public BindingAdaptor(IObservable source) + { + this._source = source; + } + + public InstancedBinding Initiate( + IAvaloniaObject target, + AvaloniaProperty targetProperty, + object anchor = null) + { + return new InstancedBinding(_source); + } + } } } diff --git a/src/Avalonia.Base/Data/IndexerBinding.cs b/src/Avalonia.Base/Data/IndexerBinding.cs new file mode 100644 index 0000000000..b2d38920d0 --- /dev/null +++ b/src/Avalonia.Base/Data/IndexerBinding.cs @@ -0,0 +1,40 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; + +namespace Avalonia.Data +{ + public class IndexerBinding : IBinding + { + public IndexerBinding( + IAvaloniaObject source, + AvaloniaProperty property, + BindingMode mode) + { + Source = source; + Property = property; + Mode = mode; + } + + private IAvaloniaObject Source { get; } + public AvaloniaProperty Property { get; } + private BindingMode Mode { get; } + + public InstancedBinding Initiate(IAvaloniaObject target, AvaloniaProperty targetProperty, object anchor = null) + { + var mode = Mode == BindingMode.Default ? + targetProperty.GetMetadata(target.GetType()).DefaultBindingMode : + Mode; + + if (mode == BindingMode.TwoWay) + { + return new InstancedBinding(Source.GetSubject(Property), mode); + } + else + { + return new InstancedBinding(Source.GetObservable(Property), mode); + } + } + } +} diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index d16f857f7c..7a42c48053 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -2,10 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Linq; -using System.Reactive.Linq; using Avalonia.Controls.Templates; -using Avalonia.Data; using Avalonia.Interactivity; using Avalonia.Logging; using Avalonia.LogicalTree; @@ -274,23 +271,6 @@ namespace Avalonia.Controls.Primitives } } - /// - protected sealed override IndexerDescriptor CreateBindingDescriptor(IndexerDescriptor source) - { - var result = base.CreateBindingDescriptor(source); - - // If the binding is a template binding, then complete when the Template changes. - if (source.Priority == BindingPriority.TemplatedParent) - { - var templateChanged = this.GetObservable(TemplateProperty).Skip(1); - - result.SourceObservable = result.Source.GetObservable(result.Property) - .TakeUntil(templateChanged); - } - - return result; - } - /// protected override IControl GetTemplateFocusTarget() { diff --git a/src/Avalonia.Diagnostics/Views/ControlDetailsView.cs b/src/Avalonia.Diagnostics/Views/ControlDetailsView.cs index 7b01e28cf4..3cde8bb49d 100644 --- a/src/Avalonia.Diagnostics/Views/ControlDetailsView.cs +++ b/src/Avalonia.Diagnostics/Views/ControlDetailsView.cs @@ -49,7 +49,7 @@ namespace Avalonia.Diagnostics.Views }, }, [GridRepeater.TemplateProperty] = pt, - [!GridRepeater.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Properties), + [!GridRepeater.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Properties).AsBinding(), } }; } @@ -62,19 +62,24 @@ namespace Avalonia.Diagnostics.Views { Text = property.Name, TextWrapping = TextWrapping.NoWrap, - [!ToolTip.TipProperty] = property.WhenAnyValue(x => x.Diagnostic), + [!ToolTip.TipProperty] = property + .WhenAnyValue(x => x.Diagnostic) + .AsBinding(), }; yield return new TextBlock { TextWrapping = TextWrapping.NoWrap, - [!TextBlock.TextProperty] = property.WhenAnyValue(v => v.Value).Select(v => v?.ToString()), + [!TextBlock.TextProperty] = property + .WhenAnyValue(v => v.Value) + .Select(v => v?.ToString()) + .AsBinding(), }; yield return new TextBlock { TextWrapping = TextWrapping.NoWrap, - [!TextBlock.TextProperty] = property.WhenAnyValue(x => x.Priority), + [!TextBlock.TextProperty] = property.WhenAnyValue(x => x.Priority).AsBinding(), }; } } diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 01092d9848..2e7db2da05 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -227,14 +227,14 @@ namespace Avalonia.Base.UnitTests public void this_Operator_Binds_One_Way() { Class1 target1 = new Class1(); - Class1 target2 = new Class1(); - IndexerDescriptor binding = Class1.FooProperty.Bind().WithMode(BindingMode.OneWay); + Class2 target2 = new Class2(); + IndexerDescriptor binding = Class2.BarProperty.Bind().WithMode(BindingMode.OneWay); target1.SetValue(Class1.FooProperty, "first"); target2[binding] = target1[!Class1.FooProperty]; target1.SetValue(Class1.FooProperty, "second"); - Assert.Equal("second", target2.GetValue(Class1.FooProperty)); + Assert.Equal("second", target2.GetValue(Class2.BarProperty)); } [Fact] @@ -242,10 +242,9 @@ namespace Avalonia.Base.UnitTests { Class1 target1 = new Class1(); Class1 target2 = new Class1(); - IndexerDescriptor binding = Class1.FooProperty.Bind().WithMode(BindingMode.TwoWay); target1.SetValue(Class1.FooProperty, "first"); - target2[binding] = target1[!Class1.FooProperty]; + target2[!Class1.FooProperty] = target1[!!Class1.FooProperty]; Assert.Equal("first", target2.GetValue(Class1.FooProperty)); target1.SetValue(Class1.FooProperty, "second"); Assert.Equal("second", target2.GetValue(Class1.FooProperty)); @@ -258,10 +257,9 @@ namespace Avalonia.Base.UnitTests { Class1 target1 = new Class1(); Class1 target2 = new Class1(); - IndexerDescriptor binding = Class1.FooProperty.Bind().WithMode(BindingMode.OneTime); target1.SetValue(Class1.FooProperty, "first"); - target2[binding] = target1[!Class1.FooProperty]; + target2[!Class1.FooProperty] = target1[Class1.FooProperty.Bind().WithMode(BindingMode.OneTime)]; target1.SetValue(Class1.FooProperty, "second"); Assert.Equal("first", target2.GetValue(Class1.FooProperty)); diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index 7e1347daf5..a685f49545 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -162,8 +162,8 @@ namespace Avalonia.Controls.UnitTests Content = new ItemsPresenter { Name = "PART_ItemsPresenter", - [~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty), - [~ItemsPresenter.ItemsPanelProperty] = parent.GetObservable(ItemsControl.ItemsPanelProperty), + [~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty).AsBinding(), + [~ItemsPresenter.ItemsPanelProperty] = parent.GetObservable(ItemsControl.ItemsPanelProperty).AsBinding(), } }); } @@ -185,7 +185,7 @@ namespace Avalonia.Controls.UnitTests new ScrollContentPresenter { Name = "PART_ContentPresenter", - [~ScrollContentPresenter.ContentProperty] = parent.GetObservable(ScrollViewer.ContentProperty), + [~ScrollContentPresenter.ContentProperty] = parent.GetObservable(ScrollViewer.ContentProperty).AsBinding(), [~~ScrollContentPresenter.ExtentProperty] = parent[~~ScrollViewer.ExtentProperty], [~~ScrollContentPresenter.OffsetProperty] = parent[~~ScrollViewer.OffsetProperty], [~~ScrollContentPresenter.ViewportProperty] = parent[~~ScrollViewer.ViewportProperty], diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index 02a6c3557d..9999fa5346 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs @@ -207,7 +207,7 @@ namespace Avalonia.Controls.UnitTests Content = new ItemsPresenter { Name = "PART_ItemsPresenter", - [~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty), + [~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty).AsBinding(), } }; } @@ -217,7 +217,7 @@ namespace Avalonia.Controls.UnitTests return new ScrollContentPresenter { Name = "PART_ContentPresenter", - [~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty), + [~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty).AsBinding(), }; } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs index d8e43e4c37..960be2ce0e 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs @@ -224,7 +224,7 @@ namespace Avalonia.Controls.UnitTests.Primitives { Child = new ContentPresenter { - [~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty), + [~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty).AsBinding(), } }; }),