Jelajahi Sumber

Merge branch 'indexer-bindings'

Steven Kirk 9 tahun lalu
induk
melakukan
56919956d9

+ 1 - 0
src/Avalonia.Base/Avalonia.Base.csproj

@@ -44,6 +44,7 @@
       <Link>Properties\SharedAssemblyInfo.cs</Link>
     </Compile>
     <Compile Include="Data\BindingError.cs" />
+    <Compile Include="Data\IndexerBinding.cs" />
     <Compile Include="Data\IValidationStatus.cs" />
     <Compile Include="Data\ObjectValidationStatus.cs" />
     <Compile Include="Diagnostics\INotifyCollectionChangedDebug.cs" />

+ 6 - 41
src/Avalonia.Base/AvaloniaObject.cs

@@ -178,59 +178,22 @@ namespace Avalonia
         /// Gets or sets a binding for a <see cref="AvaloniaProperty"/>.
         /// </summary>
         /// <param name="binding">The binding information.</param>
-        public IObservable<object> 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<ArgumentNullException>(property != null);
+            Contract.Requires<ArgumentNullException>(source != null);
+
             VerifyAccess();
 
             if (property.IsDirect)

+ 23 - 0
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@@ -16,6 +16,11 @@ namespace Avalonia
     /// </summary>
     public static class AvaloniaObjectExtensions
     {
+        public static IBinding AsBinding<T>(this IObservable<T> source)
+        {
+            return new BindingAdaptor(source.Select(x => (object)x));
+        }
+
         /// <summary>
         /// Gets an observable for a <see cref="AvaloniaProperty"/>.
         /// </summary>
@@ -293,5 +298,23 @@ namespace Avalonia
                 handler(target)(e);
             }
         }
+
+        private class BindingAdaptor : IBinding
+        {
+            private IObservable<object> _source;
+
+            public BindingAdaptor(IObservable<object> source)
+            {
+                this._source = source;
+            }
+
+            public InstancedBinding Initiate(
+                IAvaloniaObject target,
+                AvaloniaProperty targetProperty,
+                object anchor = null)
+            {
+                return new InstancedBinding(_source);
+            }
+        }
     }
 }

+ 40 - 0
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);
+            }
+        }
+    }
+}

+ 0 - 20
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
             }
         }
 
-        /// <inheritdoc/>
-        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;
-        }
-
         /// <inheritdoc/>
         protected override IControl GetTemplateFocusTarget()
         {

+ 9 - 4
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(),
             };
         }
     }

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

+ 3 - 3
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],

+ 2 - 2
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(),
             };
         }
 

+ 1 - 1
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(),
                                 }
                             };
                         }),

+ 13 - 0
tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs

@@ -315,6 +315,19 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
             Assert.Equal(2, vm.Bar);
         }
 
+        [Fact]
+        public void AvaloniaObject_this_Operator_Accepts_Binding()
+        {
+            var target = new ContentControl
+            {
+                DataContext = new { Foo = "foo" }
+            };
+
+            target[!ContentControl.ContentProperty] = new Binding("Foo");
+
+            Assert.Equal("foo", target.Content);
+        }
+
         private class TwoWayBindingTest : Control
         {
             public static readonly StyledProperty<string> TwoWayProperty =