Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented MultiBindingExpression #16219

Merged
merged 11 commits into from
Aug 7, 2024
2 changes: 1 addition & 1 deletion src/Avalonia.Base/AvaloniaObjectExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ public BindingAdaptor(IObservable<object?> source)
return new InstancedBinding(expression, BindingMode.OneWay, BindingPriority.LocalValue);
}

BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty property, object? anchor)
BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty? property, object? anchor)
{
return new UntypedObservableBindingExpression(_source, BindingPriority.LocalValue);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Data/Core/IBinding2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ internal interface IBinding2 : IBinding
{
BindingExpressionBase Instance(
AvaloniaObject target,
AvaloniaProperty targetProperty,
AvaloniaProperty? targetProperty,
object? anchor);
}
133 changes: 133 additions & 0 deletions src/Avalonia.Base/Data/Core/MultiBindingExpression.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
9 changes: 6 additions & 3 deletions src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ internal override void Attach(
internal void AttachAndStart(
IBindingExpressionSink subscriber,
AvaloniaObject target,
AvaloniaProperty targetProperty,
AvaloniaProperty? targetProperty,
BindingPriority priority)
{
AttachCore(subscriber, null, target, targetProperty, priority);
Expand Down Expand Up @@ -261,7 +261,7 @@ private void AttachCore(
IBindingExpressionSink sink,
ImmediateValueFrame? frame,
AvaloniaObject target,
AvaloniaProperty targetProperty,
AvaloniaProperty? targetProperty,
BindingPriority priority)
{
if (_sink is not null)
Expand All @@ -273,7 +273,7 @@ private void AttachCore(
_frame = frame;
_target = new(target);
TargetProperty = targetProperty;
TargetType = targetProperty.PropertyType;
TargetType = targetProperty?.PropertyType ?? typeof(object);
Priority = priority;
}

Expand Down Expand Up @@ -409,6 +409,9 @@ protected void Log(AvaloniaObject target, string error, LogEventLevel level = Lo
/// <param name="error">The new binding or data validation error.</param>
private protected void PublishValue(object? value, BindingError? error = null)
{
Debug.Assert(value is not BindingNotification);
Debug.Assert(value != BindingOperations.DoNothing);

if (!IsRunning)
return;

Expand Down
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Data/IndexerBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public IndexerBinding(
return new InstancedBinding(expression, Mode, BindingPriority.LocalValue);
}

BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty targetProperty, object? anchor)
BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor)
{
return new IndexerBindingExpression(Source, Property, target, targetProperty, Mode);
}
Expand Down
7 changes: 5 additions & 2 deletions src/Avalonia.Base/Data/TemplateBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public partial class TemplateBinding : UntypedBindingExpressionBase,
IDisposable
{
private bool _isSetterValue;
private bool _hasPublishedValue;

public TemplateBinding()
: base(BindingPriority.Template)
Expand Down Expand Up @@ -82,7 +83,7 @@ public TemplateBinding([InheritDataTypeFrom(InheritDataTypeFromScopeKind.Control
return new(target, InstanceCore(), Mode, BindingPriority.Template);
}

BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty property, object? anchor)
BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty? property, object? anchor)
{
return InstanceCore();
}
Expand All @@ -108,6 +109,7 @@ internal override bool WriteValueToSource(object? value)

protected override void StartCore()
{
_hasPublishedValue = false;
OnTemplatedParentChanged();
if (TryGetTarget(out var target))
target.PropertyChanged += OnTargetPropertyChanged;
Expand Down Expand Up @@ -199,11 +201,12 @@ private void PublishValue()

value = ConvertToTargetType(value);
PublishValue(value, error);
_hasPublishedValue = true;

if (Mode == BindingMode.OneTime)
Stop();
}
else
else if (_hasPublishedValue)
{
PublishValue(AvaloniaProperty.UnsetValue);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ public CompiledBindingExtension ProvideValue(IServiceProvider provider)
}

private protected override BindingExpressionBase Instance(
AvaloniaProperty targetProperty,
AvaloniaObject target,
AvaloniaProperty? targetProperty,
object? anchor)
{
var enableDataValidation = targetProperty.GetMetadata(target).EnableDataValidation ?? false;
var enableDataValidation = targetProperty?.GetMetadata(target).EnableDataValidation ?? false;
return InstanceCore(target, targetProperty, anchor, enableDataValidation);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public IBinding ProvideValue(IServiceProvider serviceProvider)
return new InstancedBinding(target, expression, BindingMode.OneWay, _priority);
}

BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty targetProperty, object? anchor)
BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor)
{
if (ResourceKey is null)
throw new InvalidOperationException("DynamicResource must have a ResourceKey.");
Expand Down
4 changes: 2 additions & 2 deletions src/Markup/Avalonia.Markup/Data/Binding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@ public Binding(string path, BindingMode mode = BindingMode.Default)
}

private protected override BindingExpressionBase Instance(
AvaloniaProperty targetProperty,
AvaloniaObject target,
AvaloniaProperty? targetProperty,
object? anchor)
{
var enableDataValidation = targetProperty.GetMetadata(target).EnableDataValidation ?? false;
var enableDataValidation = targetProperty?.GetMetadata(target).EnableDataValidation ?? false;
return InstanceCore(targetProperty, target, anchor, enableDataValidation);
}

Expand Down
6 changes: 3 additions & 3 deletions src/Markup/Avalonia.Markup/Data/BindingBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ public BindingBase(BindingMode mode = BindingMode.Default)
bool enableDataValidation = false);

private protected abstract BindingExpressionBase Instance(
AvaloniaProperty targetProperty,
AvaloniaObject target,
AvaloniaProperty? targetProperty,
object? anchor);

private protected (BindingMode, UpdateSourceTrigger) ResolveDefaultsFromMetadata(
Expand All @@ -118,9 +118,9 @@ private protected (BindingMode, UpdateSourceTrigger) ResolveDefaultsFromMetadata
return (mode, trigger);
}

BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty property, object? anchor)
BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty? property, object? anchor)
{
return Instance(property, target, anchor);
return Instance(target, property, anchor);
}
}
}
Loading
Loading