Skip to content

Commit 640a9dd

Browse files
grokysmaxkatz6
andauthored
Add BindingOperations.GetBindingExpressionBase. (#16214)
With basic unit tests. Co-authored-by: Max Katz <[email protected]>
1 parent ae01776 commit 640a9dd

File tree

3 files changed

+202
-0
lines changed

3 files changed

+202
-0
lines changed

src/Avalonia.Base/Data/BindingOperations.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,25 @@ public static IDisposable Apply(
101101
return Apply(target, property, binding);
102102
}
103103

104+
/// <summary>
105+
/// Retrieves the <see cref="BindingExpressionBase"/> that is currently active on the
106+
/// specified property.
107+
/// </summary>
108+
/// <param name="target">
109+
/// The <see cref="AvaloniaObject"/> from which to retrieve the binding expression.
110+
/// </param>
111+
/// <param name="property">
112+
/// The binding target property from which to retrieve the binding expression.
113+
/// </param>
114+
/// <returns>
115+
/// The <see cref="BindingExpressionBase"/> object that is active on the given property or
116+
/// null if no binding expression is active on the given property.
117+
/// </returns>
118+
public static BindingExpressionBase? GetBindingExpressionBase(AvaloniaObject target, AvaloniaProperty property)
119+
{
120+
return target.GetValueStore().GetExpression(property);
121+
}
122+
104123
private sealed class TwoWayBindingDisposable : IDisposable
105124
{
106125
private readonly IDisposable _toTargetSubscription;

src/Avalonia.Base/PropertyStore/ValueStore.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,42 @@ public T GetValue<T>(StyledProperty<T> property)
292292
return property.GetDefaultValue(Owner);
293293
}
294294

295+
public BindingExpressionBase? GetExpression(AvaloniaProperty property)
296+
{
297+
var evaluatedLocalValue = false;
298+
299+
bool TryGetLocalValue(out BindingExpressionBase? result)
300+
{
301+
if (!evaluatedLocalValue)
302+
{
303+
evaluatedLocalValue = true;
304+
305+
if (_localValueBindings?.TryGetValue(property.Id, out var o) == true)
306+
{
307+
result = o as BindingExpressionBase;
308+
return true;
309+
}
310+
}
311+
312+
result = null;
313+
return false;
314+
}
315+
316+
for (var i = _frames.Count - 1; i >= 0; --i)
317+
{
318+
var frame = _frames[i];
319+
320+
if (frame.Priority > BindingPriority.LocalValue && TryGetLocalValue(out var localExpression))
321+
return localExpression;
322+
323+
if (frame.TryGetEntryIfActive(property, out var entry, out _))
324+
return entry as BindingExpressionBase;
325+
}
326+
327+
TryGetLocalValue(out var e);
328+
return e;
329+
}
330+
295331
[MethodImpl(MethodImplOptions.AggressiveInlining)]
296332
private static EffectiveValue<T> CastEffectiveValue<T>(EffectiveValue value)
297333
{
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
using Avalonia.Controls;
2+
using Avalonia.Data;
3+
using Avalonia.Data.Converters;
4+
using Avalonia.Styling;
5+
using Avalonia.UnitTests;
6+
using Xunit;
7+
8+
namespace Avalonia.Base.UnitTests.Data;
9+
10+
public class BindingOperationsTests
11+
{
12+
[Fact]
13+
public void GetBindingExpressionBase_Returns_Null_When_Not_Bound()
14+
{
15+
var target = new Control();
16+
var expression = BindingOperations.GetBindingExpressionBase(target, Control.TagProperty);
17+
Assert.Null(expression);
18+
}
19+
20+
[Theory]
21+
[InlineData(BindingPriority.Animation)]
22+
[InlineData(BindingPriority.LocalValue)]
23+
[InlineData(BindingPriority.Style)]
24+
[InlineData(BindingPriority.StyleTrigger)]
25+
public void GetBindingExpressionBase_Returns_Expression_When_Bound(BindingPriority priority)
26+
{
27+
var data = new { Tag = "foo" };
28+
var target = new Control { DataContext = data };
29+
var binding = new Binding("Tag") { Priority = priority };
30+
target.Bind(Control.TagProperty, binding);
31+
32+
var expression = BindingOperations.GetBindingExpressionBase(target, Control.TagProperty);
33+
Assert.NotNull(expression);
34+
}
35+
36+
[Fact]
37+
public void GetBindingExpressionBase_Returns_Expression_When_Bound_Locally_With_Binding_Error()
38+
{
39+
// Target has no data context so binding will fail.
40+
var target = new Control();
41+
var binding = new Binding("Tag");
42+
target.Bind(Control.TagProperty, binding);
43+
44+
var expression = BindingOperations.GetBindingExpressionBase(target, Control.TagProperty);
45+
Assert.NotNull(expression);
46+
}
47+
48+
[Fact]
49+
public void GetBindingExpressionBase_Returns_Expression_When_Bound_To_MultiBinding()
50+
{
51+
var data = new { Tag = "foo" };
52+
var target = new Control { DataContext = data };
53+
var binding = new MultiBinding
54+
{
55+
Converter = new FuncMultiValueConverter<object, string>(x => string.Join(',', x)),
56+
Bindings =
57+
{
58+
new Binding("Tag"),
59+
new Binding("Tag"),
60+
}
61+
};
62+
63+
target.Bind(Control.TagProperty, binding);
64+
65+
var expression = BindingOperations.GetBindingExpressionBase(target, Control.TagProperty);
66+
Assert.NotNull(expression);
67+
}
68+
69+
[Fact]
70+
public void GetBindingExpressionBase_Returns_Binding_When_Bound_Via_ControlTheme()
71+
{
72+
var target = new Control();
73+
var binding = new Binding("Tag");
74+
var theme = new ControlTheme(typeof(Control))
75+
{
76+
Setters = { new Setter(Control.TagProperty, binding) },
77+
};
78+
79+
target.Theme = theme;
80+
var root = new TestRoot(target);
81+
root.UpdateLayout();
82+
83+
var expression = BindingOperations.GetBindingExpressionBase(target, Control.TagProperty);
84+
Assert.NotNull(expression);
85+
}
86+
87+
[Fact]
88+
public void GetBindingExpressionBase_Returns_Binding_When_Bound_Via_ControlTheme_TemplateBinding()
89+
{
90+
var target = new Control();
91+
var binding = new TemplateBinding(Control.TagProperty);
92+
var theme = new ControlTheme(typeof(Control))
93+
{
94+
Setters = { new Setter(Control.TagProperty, binding) },
95+
};
96+
97+
target.Theme = theme;
98+
var root = new TestRoot(target);
99+
root.UpdateLayout();
100+
101+
var expression = BindingOperations.GetBindingExpressionBase(target, Control.TagProperty);
102+
Assert.NotNull(expression);
103+
}
104+
105+
[Fact]
106+
public void GetBindingExpressionBase_Returns_Binding_When_Bound_Via_ControlTheme_Style()
107+
{
108+
var target = new Control { Classes = { "foo" } };
109+
var binding = new Binding("Tag");
110+
var theme = new ControlTheme(typeof(Control))
111+
{
112+
Children =
113+
{
114+
new Style(x => x.Nesting().Class("foo"))
115+
{
116+
Setters = { new Setter(Control.TagProperty, binding) },
117+
},
118+
}
119+
};
120+
121+
target.Theme = theme;
122+
var root = new TestRoot(target);
123+
root.UpdateLayout();
124+
125+
var expression = BindingOperations.GetBindingExpressionBase(target, Control.TagProperty);
126+
Assert.NotNull(expression);
127+
}
128+
129+
[Fact]
130+
public void GetBindingExpressionBase_Returns_Binding_When_Bound_Via_Style()
131+
{
132+
var target = new Control();
133+
var binding = new Binding("Tag");
134+
var style = new Style(x => x.OfType<Control>())
135+
{
136+
Setters = { new Setter(Control.TagProperty, binding) },
137+
};
138+
139+
var root = new TestRoot();
140+
root.Styles.Add(style);
141+
root.Child = target;
142+
root.UpdateLayout();
143+
144+
var expression = BindingOperations.GetBindingExpressionBase(target, Control.TagProperty);
145+
Assert.NotNull(expression);
146+
}
147+
}

0 commit comments

Comments
 (0)