BindingEntry.cs 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. using System;
  2. using Avalonia.Data;
  3. using Avalonia.Threading;
  4. #nullable enable
  5. namespace Avalonia.PropertyStore
  6. {
  7. /// <summary>
  8. /// Represents an untyped interface to <see cref="BindingEntry{T}"/>.
  9. /// </summary>
  10. internal interface IBindingEntry : IBatchUpdate, IPriorityValueEntry, IDisposable
  11. {
  12. void Start(bool ignoreBatchUpdate);
  13. }
  14. /// <summary>
  15. /// Stores a binding in a <see cref="ValueStore"/> or <see cref="PriorityValue{T}"/>.
  16. /// </summary>
  17. /// <typeparam name="T">The property type.</typeparam>
  18. internal class BindingEntry<T> : IBindingEntry, IPriorityValueEntry<T>, IObserver<BindingValue<T>>
  19. {
  20. private readonly IAvaloniaObject _owner;
  21. private IValueSink _sink;
  22. private IDisposable? _subscription;
  23. private bool _isSubscribed;
  24. private bool _batchUpdate;
  25. private Optional<T> _value;
  26. public BindingEntry(
  27. IAvaloniaObject owner,
  28. StyledPropertyBase<T> property,
  29. IObservable<BindingValue<T>> source,
  30. BindingPriority priority,
  31. IValueSink sink)
  32. {
  33. _owner = owner;
  34. Property = property;
  35. Source = source;
  36. Priority = priority;
  37. _sink = sink;
  38. }
  39. public StyledPropertyBase<T> Property { get; }
  40. public BindingPriority Priority { get; private set; }
  41. public IObservable<BindingValue<T>> Source { get; }
  42. Optional<object> IValue.GetValue() => _value.ToObject();
  43. public void BeginBatchUpdate() => _batchUpdate = true;
  44. public void EndBatchUpdate()
  45. {
  46. _batchUpdate = false;
  47. if (_sink is ValueStore)
  48. Start();
  49. }
  50. public Optional<T> GetValue(BindingPriority maxPriority)
  51. {
  52. return Priority >= maxPriority ? _value : Optional<T>.Empty;
  53. }
  54. public void Dispose()
  55. {
  56. _subscription?.Dispose();
  57. _subscription = null;
  58. OnCompleted();
  59. }
  60. public void OnCompleted()
  61. {
  62. var oldValue = _value;
  63. _value = default;
  64. Priority = BindingPriority.Unset;
  65. _isSubscribed = false;
  66. _sink.Completed(Property, this, oldValue);
  67. }
  68. public void OnError(Exception error)
  69. {
  70. throw new NotImplementedException();
  71. }
  72. public void OnNext(BindingValue<T> value)
  73. {
  74. if (Dispatcher.UIThread.CheckAccess())
  75. {
  76. UpdateValue(value);
  77. }
  78. else
  79. {
  80. // To avoid allocating closure in the outer scope we need to capture variables
  81. // locally. This allows us to skip most of the allocations when on UI thread.
  82. var instance = this;
  83. var newValue = value;
  84. Dispatcher.UIThread.Post(() => instance.UpdateValue(newValue));
  85. }
  86. }
  87. public void Start() => Start(false);
  88. public void Start(bool ignoreBatchUpdate)
  89. {
  90. // We can't use _subscription to check whether we're subscribed because it won't be set
  91. // until Subscribe has finished, which will be too late to prevent reentrancy. In addition
  92. // don't re-subscribe to completed/disposed bindings (indicated by Unset priority).
  93. if (!_isSubscribed &&
  94. Priority != BindingPriority.Unset &&
  95. (!_batchUpdate || ignoreBatchUpdate))
  96. {
  97. _isSubscribed = true;
  98. _subscription = Source.Subscribe(this);
  99. }
  100. }
  101. public void Reparent(IValueSink sink) => _sink = sink;
  102. public void RaiseValueChanged(
  103. IValueSink sink,
  104. IAvaloniaObject owner,
  105. AvaloniaProperty property,
  106. Optional<object> oldValue,
  107. Optional<object> newValue)
  108. {
  109. sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
  110. owner,
  111. (AvaloniaProperty<T>)property,
  112. oldValue.Cast<T>(),
  113. newValue.Cast<T>(),
  114. Priority));
  115. }
  116. private void UpdateValue(BindingValue<T> value)
  117. {
  118. if (value.HasValue && Property.ValidateValue?.Invoke(value.Value) == false)
  119. {
  120. value = Property.GetDefaultValue(_owner.GetType());
  121. }
  122. if (value.Type == BindingValueType.DoNothing)
  123. {
  124. return;
  125. }
  126. var old = _value;
  127. if (value.Type != BindingValueType.DataValidationError)
  128. {
  129. _value = value.ToOptional();
  130. }
  131. _sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(_owner, Property, old, value, Priority));
  132. }
  133. }
  134. }