Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .ncrunch/Avalonia.Diagnostics.net6.0.v3.ncrunchproject
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>
5 changes: 5 additions & 0 deletions .ncrunch/Avalonia.Diagnostics.net8.0.v3.ncrunchproject
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>
508 changes: 506 additions & 2 deletions api/Avalonia.nupkg.xml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/Avalonia.Base/Animation/AnimatorKeyFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public IDisposable BindSetter(IAnimationSetter setter, Animatable targetControl)
Property = setter.Property;
var value = setter.Value;

if (value is IBinding binding)
if (value is BindingBase binding)
{
return Bind(ValueProperty, binding, targetControl);
}
Expand Down
22 changes: 10 additions & 12 deletions src/Avalonia.Base/AvaloniaObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public object? this[AvaloniaProperty property]
/// Gets or sets a binding for a <see cref="AvaloniaProperty"/>.
/// </summary>
/// <param name="binding">The binding information.</param>
public IBinding this[IndexerDescriptor binding]
public BindingBase this[IndexerDescriptor binding]
{
get { return new IndexerBinding(this, binding.Property!, binding.Mode); }
set { this.Bind(binding.Property!, value); }
Expand Down Expand Up @@ -417,14 +417,14 @@ public void SetCurrentValue<T>(StyledProperty<T> property, T value)
}

/// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an <see cref="IBinding"/>.
/// Binds a <see cref="AvaloniaProperty"/> to an <see cref="BindingBase"/>.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="binding">The binding.</param>
/// <returns>
/// The binding expression which represents the binding instance on this object.
/// </returns>
public BindingExpressionBase Bind(AvaloniaProperty property, IBinding binding)
public BindingExpressionBase Bind(AvaloniaProperty property, BindingBase binding)
{
return Bind(property, binding, null);
}
Expand Down Expand Up @@ -474,9 +474,9 @@ public IDisposable Bind<T>(
VerifyAccess();
ValidatePriority(priority);

if (source is IBinding2 b)
if (source is BindingBase b)
{
if (b.Instance(this, property, null) is not UntypedBindingExpressionBase expression)
if (b.CreateInstance(this, property, null) is not UntypedBindingExpressionBase expression)
throw new NotSupportedException($"Binding returned unsupported {nameof(BindingExpressionBase)}.");

if (priority != expression.Priority)
Expand Down Expand Up @@ -574,9 +574,9 @@ public IDisposable Bind<T>(
throw new ArgumentException($"The property {property.Name} is readonly.");
}

if (source is IBinding2 b)
if (source is BindingBase b)
{
if (b.Instance(this, property, null) is not UntypedBindingExpressionBase expression)
if (b.CreateInstance(this, property, null) is not UntypedBindingExpressionBase expression)
throw new NotSupportedException($"Binding returned unsupported {nameof(BindingExpressionBase)}.");
return GetValueStore().AddBinding(property, expression);
}
Expand Down Expand Up @@ -643,7 +643,7 @@ public IDisposable Bind<T>(
public void CoerceValue(AvaloniaProperty property) => _values.CoerceValue(property);

/// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an <see cref="IBinding"/>.
/// Binds a <see cref="AvaloniaProperty"/> to an <see cref="BindingBase"/>.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="binding">The binding.</param>
Expand All @@ -656,11 +656,9 @@ public IDisposable Bind<T>(
/// <returns>
/// The binding expression which represents the binding instance on this object.
/// </returns>
internal BindingExpressionBase Bind(AvaloniaProperty property, IBinding binding, object? anchor)
internal BindingExpressionBase Bind(AvaloniaProperty property, BindingBase binding, object? anchor)
{
if (binding is not IBinding2 b)
throw new NotSupportedException($"Unsupported IBinding implementation '{binding}'.");
if (b.Instance(this, property, anchor) is not UntypedBindingExpressionBase expression)
if (binding.CreateInstance(this, property, anchor) is not UntypedBindingExpressionBase expression)
throw new NotSupportedException($"Binding returned unsupported {nameof(BindingExpressionBase)}.");

return GetValueStore().AddBinding(property, expression);
Expand Down
25 changes: 9 additions & 16 deletions src/Avalonia.Base/AvaloniaObjectExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ namespace Avalonia
public static class AvaloniaObjectExtensions
{
/// <summary>
/// Converts an <see cref="IObservable{T}"/> to an <see cref="IBinding"/>.
/// Converts an <see cref="IObservable{T}"/> to an <see cref="BindingBase"/>.
/// </summary>
/// <typeparam name="T">The type produced by the observable.</typeparam>
/// <param name="source">The observable</param>
/// <returns>An <see cref="IBinding"/>.</returns>
public static IBinding ToBinding<T>(this IObservable<T> source)
/// <returns>An <see cref="BindingBase"/>.</returns>
public static BindingBase ToBinding<T>(this IObservable<T> source)
{
return new BindingAdaptor(
typeof(T).IsValueType
Expand Down Expand Up @@ -228,7 +228,7 @@ public static IDisposable Bind<T>(
}

/// <summary>
/// Binds a property on an <see cref="AvaloniaObject"/> to an <see cref="IBinding"/>.
/// Binds a property on an <see cref="AvaloniaObject"/> to an <see cref="BindingBase"/>.
/// </summary>
/// <param name="target">The object.</param>
/// <param name="property">The property to bind.</param>
Expand All @@ -244,7 +244,7 @@ public static IDisposable Bind<T>(
public static IDisposable Bind(
this AvaloniaObject target,
AvaloniaProperty property,
IBinding binding,
BindingBase binding,
object? anchor = null)
{
target = target ?? throw new ArgumentNullException(nameof(target));
Expand Down Expand Up @@ -359,7 +359,7 @@ public static IDisposable AddClassHandler<TTarget, TValue>(
return observable.Subscribe(new ClassHandlerObserver<TTarget, TValue>(action));
}

private class BindingAdaptor : IBinding2
private class BindingAdaptor : BindingBase
{
private readonly IObservable<object?> _source;

Expand All @@ -368,17 +368,10 @@ public BindingAdaptor(IObservable<object?> source)
this._source = source;
}

public InstancedBinding? Initiate(
internal override BindingExpressionBase CreateInstance(
AvaloniaObject target,
AvaloniaProperty? targetProperty,
object? anchor = null,
bool enableDataValidation = false)
{
var expression = new UntypedObservableBindingExpression(_source, BindingPriority.LocalValue);
return new InstancedBinding(expression, BindingMode.OneWay, BindingPriority.LocalValue);
}

BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty? property, object? anchor)
AvaloniaProperty? property,
object? anchor)
{
return new UntypedObservableBindingExpression(_source, BindingPriority.LocalValue);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Avalonia.Base/ClassBindingManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ internal static class ClassBindingManager
private static readonly Dictionary<string, AvaloniaProperty> s_RegisteredProperties =
new Dictionary<string, AvaloniaProperty>();

public static IDisposable Bind(StyledElement target, string className, IBinding source, object anchor)
public static IDisposable Bind(StyledElement target, string className, BindingBase source, object anchor)
{
var prop = GetClassProperty(className);
return target.Bind(prop, source);
Expand Down
28 changes: 28 additions & 0 deletions src/Avalonia.Base/Data/BindingBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace Avalonia.Data;

/// <summary>
/// Base class for the various types of binding supported by Avalonia.
/// </summary>
public abstract class BindingBase
{
/// <summary>
/// Creates a <see cref="BindingExpressionBase"/> from a binding.
/// </summary>
/// <param name="target">The target of the binding.</param>
/// <param name="targetProperty">The target property of the binding.</param>
/// <param name="anchor">
/// If <paramref name="target"/> is not a control, provides an anchor object from which to
/// locate a data context or other controls.
/// </param>
/// <returns>
/// A newly instantiated <see cref="BindingExpressionBase"/>.
/// </returns>
/// <remarks>
/// This is a low-level method which returns a binding expression that is not yet connected to
/// a binding sink, and so is inactive.
/// </remarks>
internal abstract BindingExpressionBase CreateInstance(
AvaloniaObject target,
AvaloniaProperty? targetProperty,
object? anchor);
}
2 changes: 0 additions & 2 deletions src/Avalonia.Base/Data/BindingExpressionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ private protected BindingExpressionBase()
{
}

internal BindingMode Mode { get; private protected set; }

public virtual void Dispose()
{
GC.SuppressFinalize(this);
Expand Down
122 changes: 0 additions & 122 deletions src/Avalonia.Base/Data/BindingOperations.cs
Original file line number Diff line number Diff line change
@@ -1,106 +1,11 @@
using System;
using Avalonia.Diagnostics;
using Avalonia.Reactive;

namespace Avalonia.Data
{
public static class BindingOperations
{
public static readonly object DoNothing = new DoNothingType();

/// <summary>
/// Applies an <see cref="InstancedBinding"/> a property on an <see cref="AvaloniaObject"/>.
/// </summary>
/// <param name="target">The target object.</param>
/// <param name="property">The property to bind.</param>
/// <param name="binding">The instanced binding.</param>
/// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns>
[Obsolete(ObsoletionMessages.MayBeRemovedInAvalonia12)]
public static IDisposable Apply(
AvaloniaObject target,
AvaloniaProperty property,
InstancedBinding binding)
{
_ = target ?? throw new ArgumentNullException(nameof(target));
_ = property ?? throw new ArgumentNullException(nameof(property));
_ = binding ?? throw new ArgumentNullException(nameof(binding));

if (binding.Expression is { } expression)
{
return target.GetValueStore().AddBinding(property, expression);
}

var mode = binding.Mode;

if (mode == BindingMode.Default)
{
mode = property.GetMetadata(target).DefaultBindingMode;
}

switch (mode)
{
case BindingMode.Default:
case BindingMode.OneWay:
return target.Bind(property, binding.Source, binding.Priority);
case BindingMode.TwoWay:
{
if (binding.Source is not IObserver<object?> observer)
throw new InvalidOperationException("InstancedBinding does not contain a subject.");
return new TwoWayBindingDisposable(
target.Bind(property, binding.Source, binding.Priority),
target.GetObservable(property).Subscribe(observer));
}
case BindingMode.OneTime:
{
// Perf: Avoid allocating closure in the outer scope.
var targetCopy = target;
var propertyCopy = property;
var bindingCopy = binding;

return binding.Source
.Where(x => BindingNotification.ExtractValue(x) != AvaloniaProperty.UnsetValue)
.Take(1)
.Subscribe(x => targetCopy.SetValue(
propertyCopy,
BindingNotification.ExtractValue(x),
bindingCopy.Priority));
}

case BindingMode.OneWayToSource:
{
if (binding.Source is not IObserver<object?> observer)
throw new InvalidOperationException("InstancedBinding does not contain a subject.");

return Observable.CombineLatest(
binding.Source,
target.GetObservable(property),
(_, v) => v)
.Subscribe(x => observer.OnNext(x));
}

default:
throw new ArgumentException("Invalid binding mode.");
}
}

/// <summary>
/// Applies an <see cref="InstancedBinding"/> a property on an <see cref="AvaloniaObject"/>.
/// </summary>
/// <param name="target">The target object.</param>
/// <param name="property">The property to bind.</param>
/// <param name="binding">The instanced binding.</param>
/// <param name="anchor">Obsolete, unused.</param>
/// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns>
[Obsolete("Use the Apply(AvaloniaObject, AvaloniaProperty, InstancedBinding) overload.")]
public static IDisposable Apply(
AvaloniaObject target,
AvaloniaProperty property,
InstancedBinding binding,
object? anchor)
{
return Apply(target, property, binding);
}

/// <summary>
/// Retrieves the <see cref="BindingExpressionBase"/> that is currently active on the
/// specified property.
Expand All @@ -119,33 +24,6 @@ public static IDisposable Apply(
{
return target.GetValueStore().GetExpression(property);
}

private sealed class TwoWayBindingDisposable : IDisposable
{
private readonly IDisposable _toTargetSubscription;
private readonly IDisposable _fromTargetSubsription;

private bool _isDisposed;

public TwoWayBindingDisposable(IDisposable toTargetSubscription, IDisposable fromTargetSubsription)
{
_toTargetSubscription = toTargetSubscription;
_fromTargetSubsription = fromTargetSubsription;
}

public void Dispose()
{
if (_isDisposed)
{
return;
}

_fromTargetSubsription.Dispose();
_toTargetSubscription.Dispose();

_isDisposed = true;
}
}
}

public sealed class DoNothingType
Expand Down
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Data/Core/BindingExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace Avalonia.Data.Core;
/// A <see cref="BindingExpression"/> represents a untyped binding which has been
/// instantiated on an object.
/// </remarks>
internal partial class BindingExpression : UntypedBindingExpressionBase, IDescription, IDisposable
internal class BindingExpression : UntypedBindingExpressionBase, IDescription, IDisposable
{
private static readonly List<ExpressionNode> s_emptyExpressionNodes = new();
private readonly WeakReference<object?>? _source;
Expand Down
20 changes: 0 additions & 20 deletions src/Avalonia.Base/Data/Core/IBinding2.cs

This file was deleted.

Loading
Loading