diff --git a/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs b/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs
index cd8ce2cd80..d3ac3dac8a 100644
--- a/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs
+++ b/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs
@@ -36,7 +36,7 @@ namespace Avalonia.Reactive
return this;
}
- void IDisposable.Dispose()
+ public virtual void Dispose()
{
Unsubscribed();
_observer = null;
diff --git a/src/Avalonia.Styling/Styling/ISetter.cs b/src/Avalonia.Styling/Styling/ISetter.cs
index 44e43caf85..3d945c6772 100644
--- a/src/Avalonia.Styling/Styling/ISetter.cs
+++ b/src/Avalonia.Styling/Styling/ISetter.cs
@@ -16,13 +16,12 @@ namespace Avalonia.Styling
/// Instances a setter on a control.
///
/// The control.
- /// Whether the parent style has an activator.
/// An .
///
/// This method should return an which can be used to apply
/// the setter to the specified control. Note that it should not apply the setter value
- /// until is called.
+ /// until is called.
///
- ISetterInstance Instance(IStyleable target, bool hasActivator);
+ ISetterInstance Instance(IStyleable target);
}
}
diff --git a/src/Avalonia.Styling/Styling/ISetterInstance.cs b/src/Avalonia.Styling/Styling/ISetterInstance.cs
index ebfc227d12..a299a87b64 100644
--- a/src/Avalonia.Styling/Styling/ISetterInstance.cs
+++ b/src/Avalonia.Styling/Styling/ISetterInstance.cs
@@ -1,20 +1,40 @@
#nullable enable
+using System;
+
namespace Avalonia.Styling
{
///
/// Represents a setter that has been instanced on a control.
///
- public interface ISetterInstance
+ public interface ISetterInstance : IDisposable
{
+ ///
+ /// Starts the setter instance.
+ ///
+ /// Whether the parent style has an activator.
+ ///
+ /// If is false then the setter should be immediately
+ /// applied and and should not be called.
+ /// If true, then bindings etc should be initiated but not produce a value until
+ /// called.
+ ///
+ public void Start(bool hasActivator);
+
///
/// Activates the setter.
///
+ ///
+ /// Should only be called if hasActivator was true when was called.
+ ///
public void Activate();
///
/// Deactivates the setter.
///
+ ///
+ /// Should only be called if hasActivator was true when was called.
+ ///
public void Deactivate();
}
}
diff --git a/src/Avalonia.Styling/Styling/PropertySetterBindingInstance.cs b/src/Avalonia.Styling/Styling/PropertySetterBindingInstance.cs
index 74d7f98398..12055446a5 100644
--- a/src/Avalonia.Styling/Styling/PropertySetterBindingInstance.cs
+++ b/src/Avalonia.Styling/Styling/PropertySetterBindingInstance.cs
@@ -1,37 +1,85 @@
using System;
+using System.Reactive.Subjects;
using Avalonia.Data;
+using Avalonia.Reactive;
#nullable enable
namespace Avalonia.Styling
{
- internal class PropertySetterBindingInstance : ISetterInstance
+ internal class PropertySetterBindingInstance : SingleSubscriberObservableBase>,
+ ISubject>,
+ ISetterInstance
{
private readonly IStyleable _target;
- private readonly AvaloniaProperty _property;
- private readonly BindingPriority _priority;
+ private readonly StyledPropertyBase? _styledProperty;
+ private readonly DirectPropertyBase? _directProperty;
private readonly InstancedBinding _binding;
+ private readonly Inner _inner;
+ private BindingValue _value;
private IDisposable? _subscription;
+ private IDisposable? _subscriptionTwoWay;
private bool _isActive;
public PropertySetterBindingInstance(
IStyleable target,
- AvaloniaProperty property,
- BindingPriority priority,
+ StyledPropertyBase property,
IBinding binding)
{
_target = target;
- _property = property;
- _priority = priority;
- _binding = binding.Initiate(target, property).WithPriority(priority);
+ _styledProperty = property;
+ _binding = binding.Initiate(_target, property);
+ _inner = new Inner(this);
+ }
+
+ public PropertySetterBindingInstance(
+ IStyleable target,
+ DirectPropertyBase property,
+ IBinding binding)
+ {
+ _target = target;
+ _directProperty = property;
+ _binding = binding.Initiate(_target, property);
+ _inner = new Inner(this);
+ }
+
+ public void Start(bool hasActivator)
+ {
+ _isActive = !hasActivator;
+
+ if (_styledProperty is object)
+ {
+ if (_binding.Mode != BindingMode.OneWayToSource)
+ {
+ var priority = hasActivator ? BindingPriority.StyleTrigger : BindingPriority.Style;
+ _subscription = _target.Bind(_styledProperty, this, priority);
+ }
+
+ if (_binding.Mode == BindingMode.TwoWay)
+ {
+ _subscriptionTwoWay = _target.GetBindingObservable(_styledProperty).Subscribe(this);
+ }
+ }
+ else
+ {
+ if (_binding.Mode != BindingMode.OneWayToSource)
+ {
+ _subscription = _target.Bind(_directProperty!, this);
+ }
+
+ if (_binding.Mode == BindingMode.TwoWay)
+ {
+ _subscriptionTwoWay = _target.GetBindingObservable(_directProperty!).Subscribe(this);
+ }
+ }
}
public void Activate()
{
if (!_isActive)
{
- _subscription = BindingOperations.Apply(_target, _property, _binding, null);
_isActive = true;
+ PublishNext();
}
}
@@ -39,10 +87,81 @@ namespace Avalonia.Styling
{
if (_isActive)
{
- _subscription?.Dispose();
- _subscription = null;
_isActive = false;
+ PublishNext();
+ }
+ }
+
+ public override void Dispose()
+ {
+ if (_subscription is object)
+ {
+ var sub = _subscription;
+ _subscription = null;
+ sub.Dispose();
+ }
+
+ if (_subscriptionTwoWay is object)
+ {
+ var sub = _subscriptionTwoWay;
+ _subscriptionTwoWay = null;
+ sub.Dispose();
+ }
+
+ base.Dispose();
+ }
+
+ void IObserver>.OnCompleted()
+ {
+ // This is the observable coming from the target control. It should not complete.
+ }
+
+ void IObserver>.OnError(Exception error)
+ {
+ // This is the observable coming from the target control. It should not error.
+ }
+
+ void IObserver>.OnNext(BindingValue value)
+ {
+ if (value.HasValue && _isActive)
+ {
+ _binding.Subject.OnNext(value.Value);
}
}
+
+ protected override void Subscribed()
+ {
+ _subscription = _binding.Observable.Subscribe(_inner);
+ }
+
+ protected override void Unsubscribed()
+ {
+ _subscription?.Dispose();
+ _subscription = null;
+ }
+
+ private void PublishNext()
+ {
+ PublishNext(_isActive ? _value : default);
+ }
+
+ private void ConvertAndPublishNext(object? value)
+ {
+ _value = value is T v ? v : BindingValue