diff --git a/src/Avalonia.Base/Data/BindingOperations.cs b/src/Avalonia.Base/Data/BindingOperations.cs index 0b737dd959..e53ae40cb1 100644 --- a/src/Avalonia.Base/Data/BindingOperations.cs +++ b/src/Avalonia.Base/Data/BindingOperations.cs @@ -41,54 +41,41 @@ namespace Avalonia.Data { case BindingMode.Default: case BindingMode.OneWay: - if (binding.Observable is null) - throw new InvalidOperationException("InstancedBinding does not contain an observable."); - return target.Bind(property, binding.Observable, binding.Priority); + return target.Bind(property, binding.Source, binding.Priority); case BindingMode.TwoWay: - if (binding.Observable is null) - throw new InvalidOperationException("InstancedBinding does not contain an observable."); - if (binding.Subject is null) + { + if (binding.Source is not IObserver observer) throw new InvalidOperationException("InstancedBinding does not contain a subject."); return new TwoWayBindingDisposable( - target.Bind(property, binding.Observable, binding.Priority), - target.GetObservable(property).Subscribe(binding.Subject)); + target.Bind(property, binding.Source, binding.Priority), + target.GetObservable(property).Subscribe(observer)); + } case BindingMode.OneTime: - if (binding.Observable is {} source) - { - // Perf: Avoid allocating closure in the outer scope. - var targetCopy = target; - var propertyCopy = property; - var bindingCopy = binding; - - return source - .Where(x => BindingNotification.ExtractValue(x) != AvaloniaProperty.UnsetValue) - .Take(1) - .Subscribe(x => targetCopy.SetValue( - propertyCopy, - BindingNotification.ExtractValue(x), - bindingCopy.Priority)); - } - else - { - target.SetValue(property, binding.Value, binding.Priority); - return Disposable.Empty; - } + { + // Perf: Avoid allocating closure in the outer scope. + var targetCopy = target; + var propertyCopy = property; + var bindingCopy = binding; + + return binding.Source + .Where(x => BindingNotification.ExtractValue(x) != AvaloniaProperty.UnsetValue) + .Take(1) + .Subscribe(x => targetCopy.SetValue( + propertyCopy, + BindingNotification.ExtractValue(x), + bindingCopy.Priority)); + } case BindingMode.OneWayToSource: { - if (binding.Observable is null) - throw new InvalidOperationException("InstancedBinding does not contain an observable."); - if (binding.Subject is null) + if (binding.Source is not IObserver observer) throw new InvalidOperationException("InstancedBinding does not contain a subject."); - // Perf: Avoid allocating closure in the outer scope. - var bindingCopy = binding; - return Observable.CombineLatest( - binding.Observable, + binding.Source, target.GetObservable(property), (_, v) => v) - .Subscribe(x => bindingCopy.Subject.OnNext(x)); + .Subscribe(x => observer.OnNext(x)); } default: diff --git a/src/Avalonia.Base/Data/InstancedBinding.cs b/src/Avalonia.Base/Data/InstancedBinding.cs index a60c1d72ec..00e5c3d8e6 100644 --- a/src/Avalonia.Base/Data/InstancedBinding.cs +++ b/src/Avalonia.Base/Data/InstancedBinding.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Reactive; +using ObservableEx = Avalonia.Reactive.Observable; namespace Avalonia.Data { @@ -14,11 +15,23 @@ namespace Avalonia.Data /// public class InstancedBinding { - internal InstancedBinding(object? value, BindingMode mode, BindingPriority priority) + /// + /// Initializes a new instance of the class. + /// + /// The binding source. + /// The binding mode. + /// The priority of the binding. + /// + /// This constructor can be used to create any type of binding and as such requires an + /// as the binding source because this is the only binding + /// source which can be used for all binding modes. If you wish to create an instance with + /// something other than a subject, use one of the static creation methods on this class. + /// + internal InstancedBinding(IObservable source, BindingMode mode, BindingPriority priority) { Mode = mode; Priority = priority; - Value = value; + Source = source ?? throw new ArgumentNullException(nameof(source)); } /// @@ -32,24 +45,12 @@ namespace Avalonia.Data public BindingPriority Priority { get; } /// - /// Gets the value or source of the binding. - /// - public object? Value { get; } - - /// - /// Gets the as an observable. + /// Gets the binding source observable. /// - public IObservable? Observable => Value as IObservable; + public IObservable Source { get; } - /// - /// Gets the as an observer. - /// - public IObserver? Observer => Value as IObserver; - - /// - /// Gets the as an subject. - /// - internal IAvaloniaSubject? Subject => Value as IAvaloniaSubject; + [Obsolete("Use Source property")] + public IObservable Observable => Source; /// /// Creates a new one-time binding with a fixed value. @@ -61,7 +62,7 @@ namespace Avalonia.Data object value, BindingPriority priority = BindingPriority.LocalValue) { - return new InstancedBinding(value, BindingMode.OneTime, priority); + return new InstancedBinding(ObservableEx.SingleValue(value), BindingMode.OneTime, priority); } /// @@ -106,7 +107,7 @@ namespace Avalonia.Data { _ = observer ?? throw new ArgumentNullException(nameof(observer)); - return new InstancedBinding(observer, BindingMode.OneWayToSource, priority); + return new InstancedBinding((IObservable)observer, BindingMode.OneWayToSource, priority); } /// @@ -135,7 +136,7 @@ namespace Avalonia.Data /// An instance. public InstancedBinding WithPriority(BindingPriority priority) { - return new InstancedBinding(Value, Mode, priority); + return new InstancedBinding(Source, Mode, priority); } } } diff --git a/src/Avalonia.Base/Styling/Setter.cs b/src/Avalonia.Base/Styling/Setter.cs index b7b44a7dfe..093597c6a0 100644 --- a/src/Avalonia.Base/Styling/Setter.cs +++ b/src/Avalonia.Base/Styling/Setter.cs @@ -109,7 +109,7 @@ namespace Avalonia.Styling if (mode == BindingMode.OneWay || mode == BindingMode.TwoWay) { - return new PropertySetterBindingInstance(target, instance, Property, mode, i.Observable!); + return new PropertySetterBindingInstance(target, instance, Property, mode, i.Source); } throw new NotSupportedException(); diff --git a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs index e859a6e725..8f532b9803 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs @@ -7,6 +7,7 @@ using Avalonia.Data; using System; using Avalonia.Controls.Utils; using Avalonia.Markup.Xaml.MarkupExtensions; +using Avalonia.Reactive; namespace Avalonia.Controls { @@ -111,9 +112,9 @@ namespace Avalonia.Controls if (result != null) { - if(result.Subject != null) + if(result.Source is IAvaloniaSubject subject) { - var bindingHelper = new CellEditBinding(result.Subject); + var bindingHelper = new CellEditBinding(subject); var instanceBinding = new InstancedBinding(bindingHelper.InternalSubject, result.Mode, result.Priority); BindingOperations.Apply(target, property, instanceBinding, null); diff --git a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs index 1515ff2c90..993f63b4d3 100644 --- a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs @@ -85,8 +85,8 @@ namespace Avalonia.Data var children = Bindings.Select(x => x.Initiate(target, null)); - var input = children.Select(x => x?.Observable!) - .Where(x => x is not null) + var input = children.Select(x => x?.Source) + .Where(x => x is not null)! .CombineLatest() .Select(x => ConvertValue(x, targetType, converter)) .Where(x => x != BindingOperations.DoNothing); diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs index 656c2cbbbc..3ba8e8354d 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs @@ -334,7 +334,7 @@ namespace Avalonia.Markup.UnitTests.Data Path = "Foo", }; - var result = binding.Initiate(target, TextBox.TextProperty).Value; + var result = binding.Initiate(target, TextBox.TextProperty).Source; Assert.IsType(((BindingExpression)result).Converter); } @@ -350,7 +350,7 @@ namespace Avalonia.Markup.UnitTests.Data Path = "Foo", }; - var result = binding.Initiate(target, TextBox.TextProperty).Value; + var result = binding.Initiate(target, TextBox.TextProperty).Source; Assert.Same(converter.Object, ((BindingExpression)result).Converter); } @@ -367,7 +367,7 @@ namespace Avalonia.Markup.UnitTests.Data Path = "Bar", }; - var result = binding.Initiate(target, TextBox.TextProperty).Value; + var result = binding.Initiate(target, TextBox.TextProperty).Source; Assert.Same("foo", ((BindingExpression)result).ConverterParameter); } diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs index 2a0750b131..680c49d098 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs @@ -24,7 +24,7 @@ namespace Avalonia.Markup.UnitTests.Data var expressionObserver = (BindingExpression)target.Initiate( textBlock, - TextBlock.TextProperty).Observable; + TextBlock.TextProperty).Source; Assert.Same(StringConverters.IsNullOrEmpty, expressionObserver.Converter); } @@ -46,7 +46,7 @@ namespace Avalonia.Markup.UnitTests.Data var expressionObserver = (BindingExpression)target.Initiate( textBlock, - TextBlock.TextProperty).Observable; + TextBlock.TextProperty).Source; Assert.IsType(expressionObserver.Converter); } @@ -69,7 +69,7 @@ namespace Avalonia.Markup.UnitTests.Data var expressionObserver = (BindingExpression)target.Initiate( textBlock, - TextBlock.TagProperty).Observable; + TextBlock.TagProperty).Source; Assert.IsType(expressionObserver.Converter); } @@ -92,7 +92,7 @@ namespace Avalonia.Markup.UnitTests.Data var expressionObserver = (BindingExpression)target.Initiate( textBlock, - TextBlock.MarginProperty).Observable; + TextBlock.MarginProperty).Source; Assert.Same(DefaultValueConverter.Instance, expressionObserver.Converter); } diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs index 45deb97f51..505eddb146 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs @@ -20,7 +20,7 @@ namespace Avalonia.Markup.UnitTests.Data var target = new Binding(nameof(Class1.Foo)); var instanced = target.Initiate(textBlock, TextBlock.TextProperty, enableDataValidation: false); - var subject = (BindingExpression)instanced.Value; + var subject = (BindingExpression)instanced.Source; object result = null; subject.Subscribe(x => result = x); @@ -38,7 +38,7 @@ namespace Avalonia.Markup.UnitTests.Data var target = new Binding(nameof(Class1.Foo)); var instanced = target.Initiate(textBlock, TextBlock.TextProperty, enableDataValidation: true); - var subject = (BindingExpression)instanced.Value; + var subject = (BindingExpression)instanced.Source; object result = null; subject.Subscribe(x => result = x); @@ -56,7 +56,7 @@ namespace Avalonia.Markup.UnitTests.Data var target = new Binding(nameof(Class1.Foo)) { Priority = BindingPriority.Template }; var instanced = target.Initiate(textBlock, TextBlock.TextProperty, enableDataValidation: true); - var subject = (BindingExpression)instanced.Value; + var subject = (BindingExpression)instanced.Source; object result = null; subject.Subscribe(x => result = x); diff --git a/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs index a7ef2c4e4d..bf9631760a 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs @@ -30,7 +30,7 @@ namespace Avalonia.Markup.UnitTests.Data }; var target = new Control { DataContext = source }; - var observable = binding.Initiate(target, null).Observable; + var observable = binding.Initiate(target, null).Source; var result = await observable.Take(1); Assert.Equal("1,2,3", result); @@ -59,7 +59,7 @@ namespace Avalonia.Markup.UnitTests.Data }; var target = new Control { DataContext = source }; - var observable = binding.Initiate(target, null).Observable; + var observable = binding.Initiate(target, null).Source; var result = await observable.Take(1); Assert.Equal("1,2,3", result);