diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index 0e7ccbf4f3..a61cfaf5a9 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -237,7 +237,7 @@ namespace Avalonia /// can be used to provide this context. /// /// An which can be used to cancel the binding. - [Obsolete] + [Obsolete("Use AvaloniaObject.Bind(AvaloniaProperty, IBinding")] public static IDisposable Bind( this AvaloniaObject target, AvaloniaProperty property, @@ -248,20 +248,7 @@ namespace Avalonia property = property ?? throw new ArgumentNullException(nameof(property)); binding = binding ?? throw new ArgumentNullException(nameof(binding)); - var result = binding.Initiate( - target, - property, - anchor, - property.GetMetadata(target.GetType()).EnableDataValidation ?? false); - - if (result != null) - { - return BindingOperations.Apply(target, property, result, anchor); - } - else - { - return Disposable.Empty; - } + return target.Bind(property, binding); } /// @@ -384,13 +371,13 @@ namespace Avalonia object? anchor = null, bool enableDataValidation = false) { - var expression = new UntypedObservableBindingExpression(_source); + var expression = new UntypedObservableBindingExpression(_source, BindingPriority.LocalValue); return new InstancedBinding(expression, BindingMode.OneWay, BindingPriority.LocalValue); } - IBindingExpression IBinding2.Instance(AvaloniaObject target, AvaloniaProperty property) + BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty property) { - return new UntypedObservableBindingExpression(_source); + return new UntypedObservableBindingExpression(_source, BindingPriority.LocalValue); } } diff --git a/src/Avalonia.Base/Data/Core/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs index d04b067990..22be30b4b3 100644 --- a/src/Avalonia.Base/Data/Core/BindingExpression.cs +++ b/src/Avalonia.Base/Data/Core/BindingExpression.cs @@ -43,6 +43,7 @@ internal partial class BindingExpression : UntypedBindingExpressionBase, IDescri /// Whether data validation should be enabled for the binding. /// /// The binding mode. + /// The binding priority. /// The format string to use. /// The null target value. /// @@ -57,10 +58,11 @@ internal partial class BindingExpression : UntypedBindingExpressionBase, IDescri object? converterParameter = null, bool enableDataValidation = false, BindingMode mode = BindingMode.OneWay, + BindingPriority priority = BindingPriority.LocalValue, string? stringFormat = null, object? targetNullValue = null, TargetTypeConverter? targetTypeConverter = null) - : base(enableDataValidation) + : base(priority, enableDataValidation) { if (mode == BindingMode.Default) throw new ArgumentException("Binding mode cannot be Default.", nameof(mode)); @@ -154,6 +156,7 @@ internal partial class BindingExpression : UntypedBindingExpressionBase, IDescri bool enableDataValidation = false, Optional fallbackValue = default, BindingMode mode = BindingMode.OneWay, + BindingPriority priority = BindingPriority.LocalValue, object? targetNullValue = null, bool allowReflection = true) where TIn : class? @@ -170,6 +173,7 @@ internal partial class BindingExpression : UntypedBindingExpressionBase, IDescri converterParameter: converterParameter, enableDataValidation: enableDataValidation, mode: mode, + priority: priority, targetNullValue: targetNullValue, targetTypeConverter: allowReflection ? TargetTypeConverter.GetReflectionConverter() : diff --git a/src/Avalonia.Base/Data/Core/BindingExpressionBase.cs b/src/Avalonia.Base/Data/Core/BindingExpressionBase.cs index ee6f524a0a..401f8ac520 100644 --- a/src/Avalonia.Base/Data/Core/BindingExpressionBase.cs +++ b/src/Avalonia.Base/Data/Core/BindingExpressionBase.cs @@ -1,10 +1,31 @@ -using Avalonia.Styling; +using Avalonia.PropertyStore; +using Avalonia.Styling; namespace Avalonia.Data.Core; -public abstract class BindingExpressionBase : ISetterInstance +public abstract class BindingExpressionBase : IBindingExpression, ISetterInstance { private protected BindingExpressionBase() { } + + internal BindingMode Mode { get; private protected set; } + + public virtual void Dispose() + { + } + + /// + /// When overridden in a derived class, attaches the binding expression to a value store but + /// does not start it. + /// + /// The value store to attach to. + /// The target object. + /// The target property. + /// The priority of the binding. + internal abstract void Attach( + ValueStore valueStore, + AvaloniaObject target, + AvaloniaProperty targetProperty, + BindingPriority priority); } diff --git a/src/Avalonia.Base/Data/Core/IBinding2.cs b/src/Avalonia.Base/Data/Core/IBinding2.cs index 23ff3808ee..aad4f13caa 100644 --- a/src/Avalonia.Base/Data/Core/IBinding2.cs +++ b/src/Avalonia.Base/Data/Core/IBinding2.cs @@ -2,5 +2,5 @@ internal interface IBinding2 : IBinding { - IBindingExpression Instance(AvaloniaObject target, AvaloniaProperty targetProperty); + BindingExpressionBase Instance(AvaloniaObject target, AvaloniaProperty targetProperty); } diff --git a/src/Avalonia.Base/Data/Core/IndexerBindingExpression.cs b/src/Avalonia.Base/Data/Core/IndexerBindingExpression.cs index b126c91027..0a92e4b6a6 100644 --- a/src/Avalonia.Base/Data/Core/IndexerBindingExpression.cs +++ b/src/Avalonia.Base/Data/Core/IndexerBindingExpression.cs @@ -16,6 +16,7 @@ internal class IndexerBindingExpression : UntypedBindingExpressionBase AvaloniaObject target, AvaloniaProperty? targetProperty, BindingMode mode) + : base(BindingPriority.LocalValue) { _source = source; _sourceProperty = sourceProperty; diff --git a/src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs b/src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs index a0ea7b471c..2ba184a05f 100644 --- a/src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs +++ b/src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs @@ -16,7 +16,6 @@ namespace Avalonia.Data.Core; /// [PrivateApi] public abstract class UntypedBindingExpressionBase : BindingExpressionBase, - IBindingExpression, IDisposable, IDescription, IValueEntry @@ -36,9 +35,15 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase, /// /// Initializes a new instance of the class. /// + /// + /// The default binding priority for the expression. + /// /// Whether data validation is enabled. - public UntypedBindingExpressionBase(bool isDataValidationEnabled = false) + public UntypedBindingExpressionBase( + BindingPriority defaultPriority, + bool isDataValidationEnabled = false) { + Priority = defaultPriority; _isDataValidationEnabled = isDataValidationEnabled; } @@ -65,6 +70,10 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase, /// /// Gets the priority of the binding expression. /// + /// + /// Before being attached to a value store, this property describes the default priority of the + /// binding expression; this may change when the expression is attached to a value store. + /// public BindingPriority Priority { get; private set; } /// @@ -92,7 +101,7 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase, /// /// Terminates the binding. /// - public virtual void Dispose() + public override void Dispose() { if (_sink is null) return; @@ -144,7 +153,7 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase, /// /// Starts the binding expression following a call to - /// . + /// . /// public void Start() => Start(produceValue: true); @@ -177,28 +186,13 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase, void IValueEntry.Unsubscribe() => Stop(); - /// - /// Attaches the binding expression to a subscriber with the specified subscriber but does not - /// start it. - /// - /// The subscriber. - /// The target object. - /// The target property. - /// The priority of the binding. - internal void Attach( - IBindingExpressionSink sink, + internal override void Attach( + ValueStore valueStore, AvaloniaObject target, AvaloniaProperty targetProperty, BindingPriority priority) { - if (_sink is not null) - throw new InvalidOperationException("BindingExpression was already attached."); - - _sink = sink; - _target = new(target); - TargetProperty = targetProperty; - TargetType = targetProperty.PropertyType; - Priority = priority; + AttachCore(valueStore, target, targetProperty, priority); } /// @@ -215,7 +209,7 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase, AvaloniaProperty targetProperty, BindingPriority priority) { - Attach(subscriber, target, targetProperty, priority); + AttachCore(subscriber, target, targetProperty, priority); Start(produceValue: true); } @@ -285,6 +279,23 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase, /// internal virtual bool WriteValueToSource(object? value) => false; + private void AttachCore( + IBindingExpressionSink sink, + AvaloniaObject target, + AvaloniaProperty targetProperty, + BindingPriority priority) + { + if (_sink is not null) + throw new InvalidOperationException("BindingExpression was already attached."); + + _sink = sink; + _target = new(target); + TargetProperty = targetProperty; + TargetType = targetProperty.PropertyType; + Priority = priority; + } + + /// /// Converts a value using a value converter, logging a warning if necessary. /// diff --git a/src/Avalonia.Base/Data/Core/UntypedObservableBindingExpression.cs b/src/Avalonia.Base/Data/Core/UntypedObservableBindingExpression.cs index 5cfac3d443..1e26caa051 100644 --- a/src/Avalonia.Base/Data/Core/UntypedObservableBindingExpression.cs +++ b/src/Avalonia.Base/Data/Core/UntypedObservableBindingExpression.cs @@ -8,7 +8,9 @@ internal class UntypedObservableBindingExpression : UntypedBindingExpressionBase private IDisposable? _subscription; public UntypedObservableBindingExpression( - IObservable observable) + IObservable observable, + BindingPriority priority) + : base(priority) { _observable = observable; } diff --git a/src/Avalonia.Base/Data/IndexerBinding.cs b/src/Avalonia.Base/Data/IndexerBinding.cs index 8e8bda045d..90f7eb0caa 100644 --- a/src/Avalonia.Base/Data/IndexerBinding.cs +++ b/src/Avalonia.Base/Data/IndexerBinding.cs @@ -31,7 +31,7 @@ namespace Avalonia.Data return new InstancedBinding(expression, Mode, BindingPriority.LocalValue); } - IBindingExpression IBinding2.Instance(AvaloniaObject target, AvaloniaProperty targetProperty) + BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty targetProperty) { return new IndexerBindingExpression(Source, Property, target, targetProperty, Mode); } diff --git a/src/Avalonia.Base/Data/TemplateBinding.cs b/src/Avalonia.Base/Data/TemplateBinding.cs index b4561e65f7..2a1133fc9f 100644 --- a/src/Avalonia.Base/Data/TemplateBinding.cs +++ b/src/Avalonia.Base/Data/TemplateBinding.cs @@ -21,10 +21,12 @@ namespace Avalonia.Data private bool _isSetterValue; public TemplateBinding() + : base(BindingPriority.Template) { } public TemplateBinding(AvaloniaProperty property) + : base(BindingPriority.Template) { Property = property; } @@ -52,7 +54,11 @@ namespace Avalonia.Data /// /// Gets or sets the binding mode. /// - public BindingMode Mode { get; set; } + public new BindingMode Mode + { + get => base.Mode; + set => base.Mode = value; + } /// /// Gets or sets the name of the source property on the templated parent. @@ -73,7 +79,7 @@ namespace Avalonia.Data return new(target, InstanceCore(), Mode, BindingPriority.Template); } - IBindingExpression IBinding2.Instance(AvaloniaObject target, AvaloniaProperty property) + BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty property) { return InstanceCore(); } diff --git a/src/Avalonia.Base/PropertyStore/ValueFrame.cs b/src/Avalonia.Base/PropertyStore/ValueFrame.cs index 7a9d1bb13a..4d74ddcbb1 100644 --- a/src/Avalonia.Base/PropertyStore/ValueFrame.cs +++ b/src/Avalonia.Base/PropertyStore/ValueFrame.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Avalonia.Data; +using Avalonia.Data.Core; using Avalonia.Utilities; namespace Avalonia.PropertyStore diff --git a/src/Avalonia.Base/Styling/Setter.cs b/src/Avalonia.Base/Styling/Setter.cs index ec53892296..a92fe74991 100644 --- a/src/Avalonia.Base/Styling/Setter.cs +++ b/src/Avalonia.Base/Styling/Setter.cs @@ -77,8 +77,10 @@ namespace Avalonia.Styling throw new InvalidOperationException( $"Cannot set direct property '{Property}' in '{instance.Source}' because the style has an activator."); - if (Value is IBinding binding) + if (Value is IBinding2 binding) return SetBinding((StyleInstance)instance, ao, binding); + else if (Value is IBinding) + throw new AvaloniaInternalException("TODO: Make all IBindings implement IBinding2."); else if (Value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(Property.PropertyType)) return new PropertySetterTemplateInstance(Property, template); else if (!Property.IsValidValue(Value)) @@ -103,31 +105,13 @@ namespace Avalonia.Styling return Property ?? throw new InvalidOperationException("Setter.Property must be set."); } - private ISetterInstance SetBinding(StyleInstance instance, AvaloniaObject target, IBinding binding) + private ISetterInstance SetBinding(StyleInstance instance, AvaloniaObject target, IBinding2 binding) { if (!Property!.IsDirect) { - var hasDataValidation = Property.GetMetadata(target.GetType()).EnableDataValidation ?? false; - var i = binding.Initiate(target, Property, enableDataValidation: hasDataValidation)!; - var mode = i.Mode; - - if (mode == BindingMode.Default) - { - mode = Property!.GetMetadata(target.GetType()).DefaultBindingMode; - } - - if (mode == BindingMode.OneWay || mode == BindingMode.TwoWay) - { - if (i.Expression is { } expression) - { - expression.Attach(target.GetValueStore(), target, Property, instance.Priority); - return expression; - } - else - return new PropertySetterBindingInstance(target, instance, Property, mode, i.Source); - } - - throw new NotSupportedException(); + var expression = binding.Instance(target, Property); + expression.Attach(target.GetValueStore(), target, Property, instance.Priority); + return expression; } else { diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/DynamicResourceExpression.cs b/src/Markup/Avalonia.Markup.Xaml/Data/DynamicResourceExpression.cs index 6b5eab1e94..26c3e30f86 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Data/DynamicResourceExpression.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Data/DynamicResourceExpression.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; using Avalonia.Controls; +using Avalonia.Data; using Avalonia.Data.Core; using Avalonia.Logging; using Avalonia.Markup.Xaml.Converters; @@ -22,7 +23,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions public DynamicResourceExpression( object resourceKey, object? anchor, - ThemeVariant? themeVariant) + ThemeVariant? themeVariant, + BindingPriority priority) + : base(priority) { _resourceKey = resourceKey; _anchor = anchor; diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs index 38f532128c..9b933450ab 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs @@ -57,7 +57,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions return new InstancedBinding(target, expression, Mode, Priority); } - private protected override IBindingExpression Instance(AvaloniaProperty targetProperty, AvaloniaObject target) + private protected override BindingExpressionBase Instance(AvaloniaProperty targetProperty, AvaloniaObject target) { var enableDataValidation = targetProperty.GetMetadata(target.GetType()).EnableDataValidation ?? false; return InstanceCore(target, targetProperty, enableDataValidation); diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs index e26933edcc..1f4346ec89 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs @@ -1,11 +1,12 @@ using System; using Avalonia.Controls; using Avalonia.Data; +using Avalonia.Data.Core; using Avalonia.Styling; namespace Avalonia.Markup.Xaml.MarkupExtensions { - public class DynamicResourceExtension : IBinding + public class DynamicResourceExtension : IBinding2 { private object? _anchor; private BindingPriority _priority; @@ -49,8 +50,15 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions { if (ResourceKey is null) return null; - var expression = new DynamicResourceExpression(ResourceKey, _anchor, _themeVariant); + var expression = new DynamicResourceExpression(ResourceKey, _anchor, _themeVariant, _priority); return new InstancedBinding(target, expression, BindingMode.OneWay, _priority); } + + BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty targetProperty) + { + if (ResourceKey is null) + throw new InvalidOperationException("DynamicResource must have a ResourceKey."); + return new DynamicResourceExpression(ResourceKey, _anchor, _themeVariant, _priority); + } } } diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs index 4a95b57fce..9dc78483d4 100644 --- a/src/Markup/Avalonia.Markup/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup/Data/Binding.cs @@ -71,7 +71,7 @@ namespace Avalonia.Data return new InstancedBinding(target, expression, Mode, Priority); } - private protected override IBindingExpression Instance(AvaloniaProperty targetProperty, AvaloniaObject target) + private protected override BindingExpressionBase Instance(AvaloniaProperty targetProperty, AvaloniaObject target) { var enableDataValidation = targetProperty.GetMetadata(target.GetType()).EnableDataValidation ?? false; return InstanceCore(targetProperty, target, enableDataValidation); diff --git a/src/Markup/Avalonia.Markup/Data/BindingBase.cs b/src/Markup/Avalonia.Markup/Data/BindingBase.cs index 3f9e73d549..49504156cc 100644 --- a/src/Markup/Avalonia.Markup/Data/BindingBase.cs +++ b/src/Markup/Avalonia.Markup/Data/BindingBase.cs @@ -88,7 +88,7 @@ namespace Avalonia.Data object? anchor = null, bool enableDataValidation = false); - private protected abstract IBindingExpression Instance(AvaloniaProperty targetProperty, AvaloniaObject target); + private protected abstract BindingExpressionBase Instance(AvaloniaProperty targetProperty, AvaloniaObject target); private protected BindingMode ResolveBindingMode(AvaloniaObject target, AvaloniaProperty? targetProperty) { @@ -99,6 +99,6 @@ namespace Avalonia.Data return targetProperty.GetMetadata(target.GetType()).DefaultBindingMode; } - IBindingExpression IBinding2.Instance(AvaloniaObject target, AvaloniaProperty property) => Instance(property, target); + BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty property) => Instance(property, target); } } diff --git a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs index 961f5b9cca..6bd53d93a8 100644 --- a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs @@ -89,11 +89,11 @@ namespace Avalonia.Data } } - IBindingExpression IBinding2.Instance(AvaloniaObject target, AvaloniaProperty property) + BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty property) { // TODO: Implement MultiBindingExpression instead of wrapping an observable. var o = InstanceCore(target, property); - return new UntypedObservableBindingExpression(o); + return new UntypedObservableBindingExpression(o, BindingPriority.LocalValue); } private IObservable InstanceCore(AvaloniaObject target, AvaloniaProperty? targetProperty) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 8b496ad8ba..71ce8eec51 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -462,7 +462,7 @@ namespace Avalonia.Base.UnitTests var target = new Class1(); var source = new Subject(); - target.Bind(Class1.QuxProperty, new TestOneTimeBinding(source)); + target.Bind(Class1.QuxProperty, source); source.OnNext(AvaloniaProperty.UnsetValue); Assert.Equal(5.6, target.GetValue(Class1.QuxProperty)); @@ -477,7 +477,7 @@ namespace Avalonia.Base.UnitTests var target = new Class1(); var source = new Subject(); - target.Bind(Class1.QuxProperty, new TestOneTimeBinding(source)); + target.Bind(Class1.QuxProperty, source); source.OnNext(new BindingNotification(new Exception(), BindingErrorType.Error)); Assert.Equal(5.6, target.GetValue(Class1.QuxProperty)); diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests_Mode.cs b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests_Mode.cs new file mode 100644 index 0000000000..972cd71b43 --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests_Mode.cs @@ -0,0 +1,99 @@ +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Data.Core; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Base.UnitTests.Data.Core +{ + [InvariantCulture] + public class BindingExpressionTests_Mode + { + [Fact] + public void OneTime_Binding_Sets_Target_Only_Once() + { + var data = new ViewModel(); + var binding = new Binding(nameof(data.Foo), BindingMode.OneTime); + var target = new Control { DataContext = data }; + + target.Bind(Control.TagProperty, binding); + Assert.Equal("foo", target.Tag); + + data.Foo = "bar"; + Assert.Equal("foo", target.Tag); + } + + [Fact] + public void OneTime_Binding_Waits_For_DataContext() + { + var data = new ViewModel(); + var binding = new Binding(nameof(data.Foo), BindingMode.OneTime); + var target = new Control(); + + target.Bind(Control.TagProperty, binding); + Assert.Null(target.Tag); + + target.DataContext = data; + Assert.Equal("foo", target.Tag); + + data.Foo = "bar"; + Assert.Equal("foo", target.Tag); + } + + [Fact] + public void OneTime_Binding_Waits_For_DataContext_With_Matching_Property_Name() + { + var data1 = new { Baz = "baz" }; + var data2 = new ViewModel(); + var binding = new Binding(nameof(data2.Foo), BindingMode.OneTime); + var target = new Control { DataContext = data1 }; + + target.Bind(Control.TagProperty, binding); + Assert.Null(target.Tag); + + target.DataContext = data2; + Assert.Equal("foo", target.Tag); + + data2.Foo = "bar"; + Assert.Equal("foo", target.Tag); + } + + private class ViewModel : NotifyingBase + { + private string _foo; + private double _bar; + + public ViewModel(string foo = "foo", double bar = 0.5) + { + _foo = foo; + _bar = bar; + } + + public string Foo + { + get => _foo; + set + { + if (_foo != value) + { + _foo = value; + RaisePropertyChanged(); + } + } + } + + public double Bar + { + get => _bar; + set + { + if (_bar != value) + { + _bar = value; + RaisePropertyChanged(); + } + } + } + } + } +} diff --git a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs index 009a26ba18..7f52418790 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs @@ -6,7 +6,6 @@ using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Data.Converters; using Avalonia.Media; -using Avalonia.PropertyStore; using Avalonia.Styling; using Avalonia.UnitTests; using Moq; @@ -31,9 +30,7 @@ namespace Avalonia.Base.UnitTests.Styling { var control = new TextBlock(); var subject = new BehaviorSubject("foo"); - var descriptor = InstancedBinding.OneWay(subject); - var binding = Mock.Of(x => x.Initiate(control, TextBlock.TagProperty, null, false) == descriptor); - var style = Mock.Of(); + var binding = subject.ToBinding(); var setter = new Setter(TextBlock.TagProperty, binding); Apply(setter, control); @@ -46,9 +43,7 @@ namespace Avalonia.Base.UnitTests.Styling { var control = new TextBlock(); var subject = new BehaviorSubject(AvaloniaProperty.UnsetValue); - var descriptor = InstancedBinding.OneWay(subject); - var binding = Mock.Of(x => x.Initiate(control, TextBlock.TagProperty, null, false) == descriptor); - var style = Mock.Of(); + var binding = subject.ToBinding(); var setter = new Setter(TextBlock.TagProperty, binding); Apply(setter, control);