Browse Source
* Update BindingBase.Instance signature. - Swap `target` and `targetProperty` order to make it consistent with other similar methods - Make `targetProperty` nullable as it will need to be null for `MultiBinding` * IBinding2.Instance needs to accept a null target property. It will need to be null for `MultiBinding`. * Attach needs to accept a null target property. It will need to be null for `MultiBinding`. * Initial implementation of MultiBindingExpression. * Fix failing template binding test. Only publish unset value if we've already published a value. * Enabled nullability annotations. * Added passing test for #16084. * Remove obsolete API usages. * Bind to Tag not Text. Prevents test passing when it shouldn't. See https://github.com/AvaloniaUI/Avalonia/pull/16219#discussion_r1665466968 * Handle DoNothing in MultiBindingExpression.pull/16620/head
committed by
GitHub
13 changed files with 259 additions and 98 deletions
@ -0,0 +1,133 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.ObjectModel; |
|||
using System.Diagnostics; |
|||
using System.Globalization; |
|||
using Avalonia.Data.Converters; |
|||
|
|||
namespace Avalonia.Data.Core; |
|||
|
|||
internal class MultiBindingExpression : UntypedBindingExpressionBase, IBindingExpressionSink |
|||
{ |
|||
private static readonly object s_uninitialized = new object(); |
|||
private readonly IBinding[] _bindings; |
|||
private readonly IMultiValueConverter? _converter; |
|||
private readonly CultureInfo? _converterCulture; |
|||
private readonly object? _converterParameter; |
|||
private readonly UntypedBindingExpressionBase?[] _expressions; |
|||
private readonly object? _fallbackValue; |
|||
private readonly object? _targetNullValue; |
|||
private readonly object?[] _values; |
|||
private readonly ReadOnlyCollection<object?> _valuesView; |
|||
|
|||
public MultiBindingExpression( |
|||
BindingPriority priority, |
|||
IList<IBinding> bindings, |
|||
IMultiValueConverter? converter, |
|||
CultureInfo? converterCulture, |
|||
object? converterParameter, |
|||
object? fallbackValue, |
|||
object? targetNullValue) |
|||
: base(priority) |
|||
{ |
|||
_bindings = [.. bindings]; |
|||
_converter = converter; |
|||
_converterCulture = converterCulture; |
|||
_converterParameter = converterParameter; |
|||
_expressions = new UntypedBindingExpressionBase[_bindings.Length]; |
|||
_fallbackValue = fallbackValue; |
|||
_targetNullValue = targetNullValue; |
|||
_values = new object?[_bindings.Length]; |
|||
_valuesView = new(_values); |
|||
|
|||
#if NETSTANDARD2_0
|
|||
for (var i = 0; i < _bindings.Length; ++i) |
|||
_values[i] = s_uninitialized; |
|||
#else
|
|||
Array.Fill(_values, s_uninitialized); |
|||
#endif
|
|||
} |
|||
|
|||
public override string Description => "MultiBinding"; |
|||
|
|||
protected override void StartCore() |
|||
{ |
|||
if (!TryGetTarget(out var target)) |
|||
throw new AvaloniaInternalException("MultiBindingExpression has no target."); |
|||
|
|||
for (var i = 0; i < _bindings.Length; ++i) |
|||
{ |
|||
var binding = _bindings[i]; |
|||
|
|||
if (binding is not IBinding2 b) |
|||
throw new NotSupportedException($"Unsupported IBinding implementation '{binding}'."); |
|||
|
|||
var expression = b.Instance(target, null, null); |
|||
|
|||
if (expression is not UntypedBindingExpressionBase e) |
|||
throw new NotSupportedException($"Unsupported BindingExpressionBase implementation '{expression}'."); |
|||
|
|||
_expressions[i] = e; |
|||
e.AttachAndStart(this, target, null, Priority); |
|||
} |
|||
} |
|||
|
|||
protected override void StopCore() |
|||
{ |
|||
for (var i = 0; i < _expressions.Length; ++i) |
|||
{ |
|||
_expressions[i]?.Dispose(); |
|||
_expressions[i] = null; |
|||
_values[i] = s_uninitialized; |
|||
} |
|||
} |
|||
|
|||
void IBindingExpressionSink.OnChanged( |
|||
UntypedBindingExpressionBase instance, |
|||
bool hasValueChanged, |
|||
bool hasErrorChanged, |
|||
object? value, |
|||
BindingError? error) |
|||
{ |
|||
var i = Array.IndexOf(_expressions, instance); |
|||
Debug.Assert(i != -1); |
|||
|
|||
_values[i] = BindingNotification.ExtractValue(value); |
|||
PublishValue(); |
|||
} |
|||
|
|||
void IBindingExpressionSink.OnCompleted(UntypedBindingExpressionBase instance) |
|||
{ |
|||
// Nothing to do here.
|
|||
} |
|||
|
|||
private void PublishValue() |
|||
{ |
|||
foreach (var v in _values) |
|||
{ |
|||
if (v == s_uninitialized) |
|||
return; |
|||
} |
|||
|
|||
if (_converter is not null) |
|||
{ |
|||
var culture = _converterCulture ?? CultureInfo.CurrentCulture; |
|||
var converted = _converter.Convert(_valuesView, TargetType, _converterParameter, culture); |
|||
|
|||
converted = BindingNotification.ExtractValue(converted); |
|||
|
|||
if (converted != BindingOperations.DoNothing) |
|||
{ |
|||
if (converted == null) |
|||
converted = _targetNullValue; |
|||
if (converted == AvaloniaProperty.UnsetValue) |
|||
converted = _fallbackValue; |
|||
PublishValue(converted); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
PublishValue(_valuesView); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue