Skip to content

Commit

Permalink
Add BindingOperations.GetBindingExpressionBase. (#16214)
Browse files Browse the repository at this point in the history
With basic unit tests.

Co-authored-by: Max Katz <maxkatz6@outlook.com>
  • Loading branch information
grokys and maxkatz6 committed Jul 4, 2024
1 parent ae01776 commit 640a9dd
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/Avalonia.Base/Data/BindingOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,25 @@ public static IDisposable Apply(
return Apply(target, property, binding);
}

/// <summary>
/// Retrieves the <see cref="BindingExpressionBase"/> that is currently active on the
/// specified property.
/// </summary>
/// <param name="target">
/// The <see cref="AvaloniaObject"/> from which to retrieve the binding expression.
/// </param>
/// <param name="property">
/// The binding target property from which to retrieve the binding expression.
/// </param>
/// <returns>
/// The <see cref="BindingExpressionBase"/> object that is active on the given property or
/// null if no binding expression is active on the given property.
/// </returns>
public static BindingExpressionBase? GetBindingExpressionBase(AvaloniaObject target, AvaloniaProperty property)
{
return target.GetValueStore().GetExpression(property);
}

private sealed class TwoWayBindingDisposable : IDisposable
{
private readonly IDisposable _toTargetSubscription;
Expand Down
36 changes: 36 additions & 0 deletions src/Avalonia.Base/PropertyStore/ValueStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,42 @@ public T GetValue<T>(StyledProperty<T> property)
return property.GetDefaultValue(Owner);
}

public BindingExpressionBase? GetExpression(AvaloniaProperty property)
{
var evaluatedLocalValue = false;

bool TryGetLocalValue(out BindingExpressionBase? result)
{
if (!evaluatedLocalValue)
{
evaluatedLocalValue = true;

if (_localValueBindings?.TryGetValue(property.Id, out var o) == true)
{
result = o as BindingExpressionBase;
return true;
}
}

result = null;
return false;
}

for (var i = _frames.Count - 1; i >= 0; --i)
{
var frame = _frames[i];

if (frame.Priority > BindingPriority.LocalValue && TryGetLocalValue(out var localExpression))
return localExpression;

if (frame.TryGetEntryIfActive(property, out var entry, out _))
return entry as BindingExpressionBase;
}

TryGetLocalValue(out var e);
return e;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static EffectiveValue<T> CastEffectiveValue<T>(EffectiveValue value)
{
Expand Down
147 changes: 147 additions & 0 deletions tests/Avalonia.Base.UnitTests/Data/BindingOperationsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Xunit;

namespace Avalonia.Base.UnitTests.Data;

public class BindingOperationsTests
{
[Fact]
public void GetBindingExpressionBase_Returns_Null_When_Not_Bound()
{
var target = new Control();
var expression = BindingOperations.GetBindingExpressionBase(target, Control.TagProperty);
Assert.Null(expression);
}

[Theory]
[InlineData(BindingPriority.Animation)]
[InlineData(BindingPriority.LocalValue)]
[InlineData(BindingPriority.Style)]
[InlineData(BindingPriority.StyleTrigger)]
public void GetBindingExpressionBase_Returns_Expression_When_Bound(BindingPriority priority)
{
var data = new { Tag = "foo" };
var target = new Control { DataContext = data };
var binding = new Binding("Tag") { Priority = priority };
target.Bind(Control.TagProperty, binding);

var expression = BindingOperations.GetBindingExpressionBase(target, Control.TagProperty);
Assert.NotNull(expression);
}

[Fact]
public void GetBindingExpressionBase_Returns_Expression_When_Bound_Locally_With_Binding_Error()
{
// Target has no data context so binding will fail.
var target = new Control();
var binding = new Binding("Tag");
target.Bind(Control.TagProperty, binding);

var expression = BindingOperations.GetBindingExpressionBase(target, Control.TagProperty);
Assert.NotNull(expression);
}

[Fact]
public void GetBindingExpressionBase_Returns_Expression_When_Bound_To_MultiBinding()
{
var data = new { Tag = "foo" };
var target = new Control { DataContext = data };
var binding = new MultiBinding
{
Converter = new FuncMultiValueConverter<object, string>(x => string.Join(',', x)),
Bindings =
{
new Binding("Tag"),
new Binding("Tag"),
}
};

target.Bind(Control.TagProperty, binding);

var expression = BindingOperations.GetBindingExpressionBase(target, Control.TagProperty);
Assert.NotNull(expression);
}

[Fact]
public void GetBindingExpressionBase_Returns_Binding_When_Bound_Via_ControlTheme()
{
var target = new Control();
var binding = new Binding("Tag");
var theme = new ControlTheme(typeof(Control))
{
Setters = { new Setter(Control.TagProperty, binding) },
};

target.Theme = theme;
var root = new TestRoot(target);
root.UpdateLayout();

var expression = BindingOperations.GetBindingExpressionBase(target, Control.TagProperty);
Assert.NotNull(expression);
}

[Fact]
public void GetBindingExpressionBase_Returns_Binding_When_Bound_Via_ControlTheme_TemplateBinding()
{
var target = new Control();
var binding = new TemplateBinding(Control.TagProperty);
var theme = new ControlTheme(typeof(Control))
{
Setters = { new Setter(Control.TagProperty, binding) },
};

target.Theme = theme;
var root = new TestRoot(target);
root.UpdateLayout();

var expression = BindingOperations.GetBindingExpressionBase(target, Control.TagProperty);
Assert.NotNull(expression);
}

[Fact]
public void GetBindingExpressionBase_Returns_Binding_When_Bound_Via_ControlTheme_Style()
{
var target = new Control { Classes = { "foo" } };
var binding = new Binding("Tag");
var theme = new ControlTheme(typeof(Control))
{
Children =
{
new Style(x => x.Nesting().Class("foo"))
{
Setters = { new Setter(Control.TagProperty, binding) },
},
}
};

target.Theme = theme;
var root = new TestRoot(target);
root.UpdateLayout();

var expression = BindingOperations.GetBindingExpressionBase(target, Control.TagProperty);
Assert.NotNull(expression);
}

[Fact]
public void GetBindingExpressionBase_Returns_Binding_When_Bound_Via_Style()
{
var target = new Control();
var binding = new Binding("Tag");
var style = new Style(x => x.OfType<Control>())
{
Setters = { new Setter(Control.TagProperty, binding) },
};

var root = new TestRoot();
root.Styles.Add(style);
root.Child = target;
root.UpdateLayout();

var expression = BindingOperations.GetBindingExpressionBase(target, Control.TagProperty);
Assert.NotNull(expression);
}
}

0 comments on commit 640a9dd

Please sign in to comment.