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

Fix bindings without property path #16729

Merged
merged 3 commits into from
Sep 14, 2024
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 src/Avalonia.Base/Data/Core/BindingExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,11 @@ internal void OnNodeValueChanged(int nodeIndex, object? value, Exception? dataVa

if (nodeIndex == _nodes.Count - 1)
{
// If the binding source is a data context without any path and is currently null, treat it as an invalid
// value. This allows bindings to DataContext and DataContext.Property to share the same behavior.
if (value is null && _nodes[nodeIndex] is DataContextNodeBase)
value = AvaloniaProperty.UnsetValue;

// The leaf node has changed. If the binding mode is not OneWayToSource, publish the
// value to the target.
if (_mode != BindingMode.OneWayToSource)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ public void TargetNullValue_Should_Be_Used_When_Source_String_Is_Null()
GC.KeepAlive(data);
}

[Fact]
public void TargetNullValue_Should_Not_Be_Used_When_Source_Is_Data_Context_And_Null()
{
var target = CreateTarget<string?, string?>(
o => o,
targetNullValue: "bar");

Assert.Equal(null, target.String);
}

[Fact]
public void Can_Use_UpdateTarget_To_Update_From_Non_INPC_Data()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,31 @@ public void OneTime_Binding_Waits_For_DataContext_With_Matching_Property_Type()
Assert.Equal(0.5, target.Double);
}

[Fact]
public void OneTime_Binding_Waits_For_DataContext_Without_Property_Path()
{
var target = CreateTarget<string?, string?>(
x => x,
mode: BindingMode.OneTime);

target.DataContext = "foo";

Assert.Equal("foo", target.String);
}

[Fact]
public void OneTime_Binding_Waits_For_DataContext_Without_Property_Path_With_StringFormat()
{
var target = CreateTarget<string?, string?>(
x => x,
mode: BindingMode.OneTime,
stringFormat: "bar: {0}");

target.DataContext = "foo";

Assert.Equal("bar: foo", target.String);
}

[Fact]
public void OneWayToSource_Binding_Updates_Source_When_Target_Changes()
{
Expand Down
15 changes: 13 additions & 2 deletions tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ private protected override (TargetClass, BindingExpression) CreateTargetCore<TIn
RelativeSource? relativeSource,
Optional<TIn> source,
object? targetNullValue,
string? stringFormat,
UpdateSourceTrigger updateSourceTrigger)
{
var target = new TargetClass { DataContext = dataContext };
Expand Down Expand Up @@ -66,6 +67,7 @@ private protected override (TargetClass, BindingExpression) CreateTargetCore<TIn
mode: mode,
targetNullValue: targetNullValue,
targetTypeConverter: TargetTypeConverter.GetReflectionConverter(),
stringFormat: stringFormat,
updateSourceTrigger: updateSourceTrigger);

target.GetValueStore().AddBinding(targetProperty, bindingExpression);
Expand All @@ -87,6 +89,7 @@ private protected override (TargetClass, BindingExpression) CreateTargetCore<TIn
RelativeSource? relativeSource,
Optional<TIn> source,
object? targetNullValue,
string? stringFormat,
UpdateSourceTrigger updateSourceTrigger)
{
var target = new TargetClass { DataContext = dataContext };
Expand All @@ -112,6 +115,7 @@ private protected override (TargetClass, BindingExpression) CreateTargetCore<TIn
mode: mode,
targetNullValue: targetNullValue,
targetTypeConverter: TargetTypeConverter.GetReflectionConverter(),
stringFormat: stringFormat,
updateSourceTrigger: updateSourceTrigger);
target.GetValueStore().AddBinding(targetProperty, bindingExpression);
return (target, bindingExpression);
Expand All @@ -129,7 +133,8 @@ protected TargetClass CreateTarget<TIn, TOut>(
BindingMode mode = BindingMode.OneWay,
RelativeSource? relativeSource = null,
Optional<TIn> source = default,
object? targetNullValue = null)
object? targetNullValue = null,
string? stringFormat = null)
where TIn : class?
{
var (target, _) = CreateTargetAndExpression(
Expand All @@ -143,7 +148,8 @@ protected TargetClass CreateTarget<TIn, TOut>(
mode,
relativeSource,
source,
targetNullValue);
targetNullValue,
stringFormat);
return target;
}

Expand All @@ -158,6 +164,7 @@ protected TargetClass CreateTargetWithSource<TIn, TOut>(
BindingMode mode = BindingMode.OneWay,
RelativeSource? relativeSource = null,
object? targetNullValue = null,
string? stringFormat = null,
UpdateSourceTrigger updateSourceTrigger = UpdateSourceTrigger.PropertyChanged)
where TIn : class?
{
Expand All @@ -173,6 +180,7 @@ protected TargetClass CreateTargetWithSource<TIn, TOut>(
relativeSource,
source,
targetNullValue,
stringFormat,
updateSourceTrigger);
return target;
}
Expand All @@ -189,6 +197,7 @@ private protected (TargetClass, BindingExpression) CreateTargetAndExpression<TIn
RelativeSource? relativeSource = null,
Optional<TIn> source = default,
object? targetNullValue = null,
string? stringFormat = null,
UpdateSourceTrigger updateSourceTrigger = UpdateSourceTrigger.PropertyChanged)
where TIn : class?
{
Expand All @@ -213,6 +222,7 @@ private protected (TargetClass, BindingExpression) CreateTargetAndExpression<TIn
relativeSource,
source,
targetNullValue,
stringFormat,
updateSourceTrigger);
}

Expand All @@ -228,6 +238,7 @@ private protected abstract (TargetClass, BindingExpression) CreateTargetCore<TIn
RelativeSource? relativeSource,
Optional<TIn> source,
object? targetNullValue,
string? stringFormat,
UpdateSourceTrigger updateSourceTrigger)
where TIn : class?;

Expand Down
6 changes: 3 additions & 3 deletions tests/Avalonia.Markup.UnitTests/Data/BindingTests_Logging.cs
Original file line number Diff line number Diff line change
Expand Up @@ -305,16 +305,16 @@ public void Should_Log_Invalid_FallbackValue(bool rooted)
[InlineData(false)]
public void Should_Log_Invalid_TargetNullValue(bool rooted)
{
var target = new Decorator { };
var binding = new Binding() { TargetNullValue = "foo" };
var target = new Decorator { DataContext = new { Bar = (string?) null } };
var binding = new Binding("Bar") { TargetNullValue = "foo" };

if (rooted)
new TestRoot(target);

// An invalid target null value is invalid whether the control is rooted or not.
using (AssertLog(
target,
"",
binding.Path,
"Could not convert TargetNullValue 'foo' to 'System.Double'.",
level: LogEventLevel.Error,
property: Visual.OpacityProperty))
Expand Down
Loading