Skip to content

Commit c1f8c19

Browse files
committed
fix(xBind): Adjust FallbackValue behavior to explicitly support setting null to the target
1 parent 5b2e1b8 commit c1f8c19

File tree

5 files changed

+142
-4
lines changed

5 files changed

+142
-4
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<UserControl x:Class="Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls.xBind_ValueType"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:local="using:Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls"
5+
xmlns:xc="using:Windows.UI.Xaml.Controls"
6+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
7+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
8+
mc:Ignorable="d"
9+
d:DesignHeight="300"
10+
d:DesignWidth="400">
11+
12+
<Grid>
13+
<TextBlock x:Name="tb1"
14+
x:FieldModifier="public"
15+
Tag="{x:Bind VM.Model2.MyDateTime, Mode=OneWay, FallbackValue={x:Null}}" />
16+
</Grid>
17+
</UserControl>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Runtime.InteropServices.WindowsRuntime;
7+
using Windows.Foundation;
8+
using Windows.Foundation.Collections;
9+
using Windows.UI.Xaml;
10+
using Windows.UI.Xaml.Controls;
11+
using Windows.UI.Xaml.Controls.Primitives;
12+
using Windows.UI.Xaml.Data;
13+
using Windows.UI.Xaml.Input;
14+
using Windows.UI.Xaml.Media;
15+
using Windows.UI.Xaml.Navigation;
16+
17+
// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
18+
19+
namespace Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls
20+
{
21+
public sealed partial class xBind_ValueType : UserControl
22+
{
23+
public xBind_ValueType()
24+
{
25+
this.InitializeComponent();
26+
}
27+
28+
public xBind_ValueType_MyModel1 VM { get; } = new();
29+
}
30+
31+
public class xBind_ValueType_MyModel1 : System.ComponentModel.INotifyPropertyChanged
32+
{
33+
private xBind_ValueType_MyModel2 model2;
34+
35+
public xBind_ValueType_MyModel2 Model2
36+
{
37+
get => model2;
38+
set
39+
{
40+
model2 = value;
41+
PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(nameof(Model2)));
42+
}
43+
}
44+
45+
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
46+
}
47+
48+
public class xBind_ValueType_MyModel2 : System.ComponentModel.INotifyPropertyChanged
49+
{
50+
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
51+
52+
private DateTime myDateTime;
53+
54+
public DateTime MyDateTime
55+
{
56+
get => myDateTime;
57+
set
58+
{
59+
myDateTime = value;
60+
PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(nameof(MyDateTime)));
61+
}
62+
}
63+
}
64+
}

src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Given_xBind_Binding.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,6 +1299,23 @@ public async Task When_AttachedProperty()
12991299
Assert.AreEqual(42, SUT.tb1.Tag);
13001300
}
13011301

1302+
[TestMethod]
1303+
public async Task When_ValueType()
1304+
{
1305+
var SUT = new xBind_ValueType();
1306+
var date1 = new DateTime(2022, 10, 01);
1307+
1308+
SUT.VM.Model2 = new() { MyDateTime = date1 };
1309+
1310+
SUT.ForceLoaded();
1311+
1312+
Assert.AreEqual(date1, SUT.tb1.Tag);
1313+
1314+
SUT.VM.Model2 = null;
1315+
1316+
Assert.IsNull(SUT.tb1.Tag);
1317+
}
1318+
13021319
private async Task AssertIsNullAsync<T>(Func<T> getter, TimeSpan? timeout = null) where T:class
13031320
{
13041321
timeout ??= TimeSpan.FromSeconds(1);

src/Uno.UI/DataBinding/BindingExpression.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,8 @@ void applySource()
335335

336336
private void ApplyFallbackValue(bool useTypeDefaultValue = true)
337337
{
338-
if (ParentBinding.FallbackValue != null)
338+
if (ParentBinding.FallbackValue != null
339+
|| (ParentBinding.CompiledSource != null && ParentBinding.IsFallbackValueSet))
339340
{
340341
SetTargetValue(ConvertToBoundPropertyType(ParentBinding.FallbackValue));
341342
}

src/Uno.UI/UI/Xaml/Data/Binding.cs

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ public partial class Binding : BindingBase
4040
/// </summary>
4141
private object _source;
4242

43+
/// <summary>
44+
/// Storage for the FallbackValue property
45+
/// </summary>
46+
private object _fallbackValue;
47+
48+
/// <summary>
49+
/// A set of flags for this instance
50+
/// </summary>
51+
private BindingFlags _flags;
52+
4353
public Binding()
4454
{
4555

@@ -61,9 +71,9 @@ public Binding()
6171
Mode = BindingMode.OneWay;
6272
}
6373

64-
public static implicit operator Binding (string path)
74+
public static implicit operator Binding(string path)
6575
{
66-
return new Binding (path);
76+
return new Binding(path);
6777
}
6878

6979
/// <summary>
@@ -100,7 +110,18 @@ public static implicit operator Binding (string path)
100110
/// Gets or sets the value to use when the binding is unable to return a value.
101111
/// </summary>
102112
/// <value>The fallback value.</value>
103-
public object FallbackValue { get; set; }
113+
public object FallbackValue
114+
{
115+
get => _fallbackValue;
116+
set
117+
{
118+
_fallbackValue = value;
119+
120+
// Mark the value as set, regardless of the value itself
121+
// so x:Bind can set that value to the target.
122+
_flags |= BindingFlags.FallbackValueSet;
123+
}
124+
}
104125

105126
/// <summary>
106127
/// Gets or sets a value that indicates the direction of the data flow in the binding.
@@ -203,6 +224,24 @@ internal void SetBindingXBindProvider(object compiledSource, Func<object, object
203224
XBindPropertyPaths = propertyPaths;
204225
XBindBack = xBindBack;
205226
}
227+
228+
/// <summary>
229+
/// Determines if the FallbackValue has been set
230+
/// </summary>
231+
/// <remarks>To be used for x:Bind only</remarks>
232+
internal bool IsFallbackValueSet
233+
=> _flags.HasFlag(BindingFlags.FallbackValueSet);
234+
235+
[Flags]
236+
private enum BindingFlags
237+
{
238+
None = 0,
239+
240+
/// <summary>
241+
/// Determines if the FallbackValue has been set.
242+
/// </summary>
243+
FallbackValueSet = 1,
244+
}
206245
}
207246
}
208247

0 commit comments

Comments
 (0)