A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

148 lines
4.6 KiB

using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
internal abstract class BindingEntryBase<TValue, TSource> : IValueEntry<TValue>,
IObserver<TSource>,
IObserver<BindingValue<TSource>>,
IDisposable
{
private static readonly IDisposable s_creating = Disposable.Empty;
private static readonly IDisposable s_creatingQuiet = Disposable.Create(() => { });
private IDisposable? _subscription;
private bool _hasValue;
private TValue? _value;
protected BindingEntryBase(
ValueFrame frame,
AvaloniaProperty property,
IObservable<BindingValue<TSource>> source)
{
Frame = frame;
Source = source;
Property = property;
}
protected BindingEntryBase(
ValueFrame frame,
AvaloniaProperty property,
IObservable<TSource> source)
{
Frame = frame;
Source = source;
Property = property;
}
public bool HasValue
{
get
{
Start(produceValue: false);
return _hasValue;
}
}
public bool IsSubscribed => _subscription is not null;
public AvaloniaProperty Property { get; }
AvaloniaProperty IValueEntry.Property => Property;
protected ValueFrame Frame { get; }
protected object Source { get; }
public void Dispose()
{
Unsubscribe();
BindingCompleted();
}
public TValue GetValue()
{
Start(produceValue: false);
if (!_hasValue)
throw new AvaloniaInternalException("The binding entry has no value.");
return _value!;
}
public void Start() => Start(true);
public void OnCompleted() => BindingCompleted();
public void OnError(Exception error) => BindingCompleted();
public void OnNext(TSource value) => SetValue(ConvertAndValidate(value));
public void OnNext(BindingValue<TSource> value) => SetValue(ConvertAndValidate(value));
public virtual void Unsubscribe()
{
_subscription?.Dispose();
_subscription = null;
}
object? IValueEntry.GetValue()
{
Start(produceValue: false);
if (!_hasValue)
throw new AvaloniaInternalException("The BindingEntry<T> has no value.");
return _value!;
}
protected abstract BindingValue<TValue> ConvertAndValidate(TSource value);
protected abstract BindingValue<TValue> ConvertAndValidate(BindingValue<TSource> value);
protected virtual void Start(bool produceValue)
{
if (_subscription is not null)
return;
_subscription = produceValue ? s_creating : s_creatingQuiet;
_subscription = Source switch
{
IObservable<BindingValue<TSource>> bv => bv.Subscribe(this),
IObservable<TSource> b => b.Subscribe(this),
_ => throw new AvaloniaInternalException("Unexpected binding source."),
};
}
private void ClearValue()
{
if (_hasValue)
{
_hasValue = false;
_value = default;
if (_subscription is not null)
Frame.Owner?.OnBindingValueCleared(Property, Frame.Priority);
}
}
private void SetValue(BindingValue<TValue> value)
{
if (Frame.Owner is null)
return;
LoggingUtils.LogIfNecessary(Frame.Owner.Owner, Property, value);
if (value.HasValue)
{
if (!_hasValue || !EqualityComparer<TValue>.Default.Equals(_value, value.Value))
{
_value = value.Value;
_hasValue = true;
if (_subscription is not null && _subscription != s_creatingQuiet)
Frame.Owner?.OnBindingValueChanged(this, Frame.Priority);
}
}
else if (value.Type != BindingValueType.DoNothing)
{
ClearValue();
if (_subscription is not null && _subscription != s_creatingQuiet)
Frame.Owner?.OnBindingValueCleared(Property, Frame.Priority);
}
}
private void BindingCompleted()
{
_subscription = null;
Frame.OnBindingCompleted(this);
}
}
}