diff --git a/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Converter_TwoWay.xaml b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Converter_TwoWay.xaml
new file mode 100644
index 000000000000..5d7661110f27
--- /dev/null
+++ b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Converter_TwoWay.xaml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Converter_TwoWay.xaml.cs b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Converter_TwoWay.xaml.cs
new file mode 100644
index 000000000000..e1912b0344fd
--- /dev/null
+++ b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Converter_TwoWay.xaml.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+
+// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
+
+namespace Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls
+{
+ ///
+ /// An empty page that can be used on its own or navigated to within a Frame.
+ ///
+ public sealed partial class Binding_Converter_TwoWay : Page
+ {
+ public TestViewModel ViewModel { get; }
+
+ public Binding_Converter_TwoWay()
+ {
+ this.InitializeComponent();
+
+ DataContext = ViewModel = new TestViewModel() { ShowImages = true };
+
+ //Loaded += MainPage_Loaded;
+ }
+
+ //private void MainPage_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ //{
+ //}
+
+ public class TestViewModel : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public bool ShowImages
+ {
+ get { return _showImages; }
+ set
+ {
+ if (_showImages != value)
+ {
+ _showImages = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ShowImages)));
+ }
+ }
+ }
+ bool _showImages = false;
+ }
+ }
+}
diff --git a/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/BoolToIntegerConverter.cs b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/BoolToIntegerConverter.cs
new file mode 100644
index 000000000000..a8aeef58945f
--- /dev/null
+++ b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/BoolToIntegerConverter.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.UI.Xaml.Data;
+
+namespace Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls
+{
+ public class BoolToIntegerConverter : IValueConverter
+ {
+ public bool IsInverse { get; set; }
+
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ var boolValue = value as bool?;
+ if (!boolValue.HasValue)
+ {
+ return null;
+ }
+
+ if (IsInverse)
+ {
+ boolValue = !boolValue.Value;
+ }
+
+ return boolValue.Value ? 1 : 0;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ var intValue = value as int?;
+ if (!intValue.HasValue)
+ {
+ return null;
+ }
+
+ var result = intValue.Value >= 1;
+
+ if (IsInverse)
+ {
+ result = !result;
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/InverseBoolConverter.cs b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/InverseBoolConverter.cs
new file mode 100644
index 000000000000..f29b53c8cb5f
--- /dev/null
+++ b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/InverseBoolConverter.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.UI.Xaml.Data;
+
+namespace Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls
+{
+ public class InverseBoolConverter : IValueConverter
+ {
+ public object Convert(object value, System.Type targetType, object parameter, string language)
+ {
+ if (value is bool)
+ {
+ return !(bool)value;
+ }
+
+ return null;
+ }
+
+ public object ConvertBack(object value, System.Type targetType, object parameter, string language)
+ {
+ if (value is bool)
+ {
+ return !(bool)value;
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Given_xBind_Binding.cs b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Given_xBind_Binding.cs
index b1ea384c4d30..0a3a5d1fa1cd 100644
--- a/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Given_xBind_Binding.cs
+++ b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Given_xBind_Binding.cs
@@ -142,7 +142,7 @@ public void When_DataTemplate_TwoWay_Nested()
Assert.AreEqual(2, rootData.Model.MyIntProperty);
}
-
+
[TestMethod]
public void When_Object_TwoWay()
{
@@ -344,6 +344,31 @@ public void When_Converter()
Assert.AreEqual("v:Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls.Binding_Converter p:test", SUT.myTextBlock2.Text);
}
+ [TestMethod]
+ public void When_Converter_TwoWay()
+ {
+ var SUT = new Binding_Converter_TwoWay();
+
+ SUT.ForceLoaded();
+
+ ListView list = SUT.ViewToggleListView;
+
+ CheckBox cb = SUT.BoundCheckBox;
+
+ Assert.AreEqual(0, list.SelectedIndex);
+ Assert.IsTrue(cb.IsChecked.Value);
+
+ list.SelectedItem = list.Items[1];
+
+ Assert.AreEqual(1, list.SelectedIndex);
+ Assert.IsFalse(cb.IsChecked.Value);
+
+ list.SelectedItem = list.Items[0];
+
+ Assert.AreEqual(0, list.SelectedIndex);
+ Assert.IsTrue(cb.IsChecked.Value);
+ }
+
[TestMethod]
public void When_ConverterParameter()
{
@@ -479,7 +504,7 @@ public void When_DefaultBindingMode_TwoWay()
Assert.AreEqual("TwoWay updated 5", SUT.Default_TwoWay_OneWay_Property);
Assert.AreEqual("TwoWay updated 9", SUT.Default_TwoWay_TwoWay_Property);
}
-
+
[TestMethod]
public void When_DefaultBindingMode_Nested()
{
diff --git a/src/Uno.UI/DataBinding/BindingPath.cs b/src/Uno.UI/DataBinding/BindingPath.cs
index 0887a6cdd1b9..382fa3703914 100644
--- a/src/Uno.UI/DataBinding/BindingPath.cs
+++ b/src/Uno.UI/DataBinding/BindingPath.cs
@@ -1,4 +1,6 @@
-#if !NETFX_CORE
+#nullable enable
+
+#if !NETFX_CORE
using Uno.UI.DataBinding;
using Uno.Extensions;
using System;
@@ -23,9 +25,9 @@ internal class BindingPath : IDisposable, IValueChangedListener
private static List _propertyChangedHandlers = new List();
private readonly string _path;
- private BindingItem _chain;
- private BindingItem _value;
- private ManagedWeakReference _dataContextWeakStorage;
+ private BindingItem? _chain;
+ private BindingItem? _value;
+ private ManagedWeakReference? _dataContextWeakStorage;
private bool _disposed;
///
@@ -35,7 +37,7 @@ internal class BindingPath : IDisposable, IValueChangedListener
/// The property in the datacontext
/// The action to execute when a new value is raised
/// A disposable that will cleanup resources.
- public delegate IDisposable PropertyChangedRegistrationHandler(ManagedWeakReference dataContext, string propertyName, Action onNewValue);
+ public delegate IDisposable? PropertyChangedRegistrationHandler(ManagedWeakReference dataContext, string propertyName, Action onNewValue);
///
/// Provides the new values for the current binding.
@@ -43,7 +45,7 @@ internal class BindingPath : IDisposable, IValueChangedListener
///
/// This event is not a generic type for performance constraints on Mono's Full-AOT
///
- public IValueChangedListener ValueChangedListener { get; set; }
+ public IValueChangedListener? ValueChangedListener { get; set; }
static BindingPath()
{
@@ -55,7 +57,7 @@ static BindingPath()
///
///
/// Provides the fallback value to apply when the source is invalid.
- public BindingPath(string path, object fallbackValue) :
+ public BindingPath(string path, object? fallbackValue) :
this(path, fallbackValue, null, false)
{
}
@@ -67,7 +69,7 @@ public BindingPath(string path, object fallbackValue) :
/// Provides the fallback value to apply when the source is invalid.
/// The precedence value to manipulate if the path matches to a DependencyProperty.
/// Allows for the binding engine to include private properties in the lookup
- internal BindingPath(string path, object fallbackValue, DependencyPropertyValuePrecedences? precedence, bool allowPrivateMembers)
+ internal BindingPath(string path, object? fallbackValue, DependencyPropertyValuePrecedences? precedence, bool allowPrivateMembers)
{
_path = path ?? "";
@@ -89,7 +91,7 @@ internal BindingPath(string path, object fallbackValue, DependencyPropertyValueP
///
public IEnumerable GetPathItems()
{
- return _chain.Flatten(i => i.Next);
+ return _chain?.Flatten(i => i.Next!) ?? new BindingItem[0];
}
///
@@ -137,7 +139,7 @@ public string Path
/// Provides the value of the using the
/// current using the current precedence.
///
- public object Value
+ public object? Value
{
get
{
@@ -165,7 +167,7 @@ public object Value
///
/// Name of the leaf property in the path.
///
- internal string LeafPropertyName => _value?.PropertyName;
+ internal string? LeafPropertyName => _value?.PropertyName;
///
/// Gets the value of the DependencyProperty with a
@@ -173,7 +175,7 @@ public object Value
/// of the BindingPath.
///
/// The lower precedence value
- public object GetSubstituteValue()
+ public object? GetSubstituteValue()
{
if (_value != null)
{
@@ -211,11 +213,11 @@ public void ClearValue()
}
}
- public Type ValueType => _value.PropertyType;
+ public Type? ValueType => _value?.PropertyType;
- internal object DataItem => _value.DataContext;
+ internal object? DataItem => _value?.DataContext;
- public object DataContext
+ public object? DataContext
{
get => _dataContextWeakStorage?.Target;
set => SetWeakDataContext(Uno.UI.DataBinding.WeakReferencePool.RentWeakReference(this, value));
@@ -283,8 +285,8 @@ protected virtual void Dispose(bool disposing)
///
private static void Parse(
string path,
- object fallbackValue, DependencyPropertyValuePrecedences? precedence, bool allowPrivateMembers,
- ref BindingItem head, ref BindingItem tail)
+ object? fallbackValue, DependencyPropertyValuePrecedences? precedence, bool allowPrivateMembers,
+ ref BindingItem? head, ref BindingItem? tail)
{
var propertyLength = 0;
bool isInAttachedProperty = false, isInItemIndex = false;
@@ -340,8 +342,8 @@ private static void Parse(
///
private static void TryPrependItem(
string path, int start, int length,
- object fallbackValue, DependencyPropertyValuePrecedences? precedence, bool allowPrivateMembers,
- ref BindingItem head, ref BindingItem tail)
+ object? fallbackValue, DependencyPropertyValuePrecedences? precedence, bool allowPrivateMembers,
+ ref BindingItem? head, ref BindingItem? tail)
{
if (length <= 0)
{
@@ -372,7 +374,7 @@ private static void TryPrependItem(
///
/// Subscribes for updates to the INotifyPropertyChanged interface.
///
- private static IDisposable SubscribeToNotifyPropertyChanged(ManagedWeakReference dataContextReference, string propertyName, Action newValueAction)
+ private static IDisposable? SubscribeToNotifyPropertyChanged(ManagedWeakReference dataContextReference, string propertyName, Action newValueAction)
{
// Attach to the Notify property changed events
var notify = dataContextReference.Target as System.ComponentModel.INotifyPropertyChanged;
@@ -426,30 +428,30 @@ private static IDisposable SubscribeToNotifyPropertyChanged(ManagedWeakReference
private sealed class BindingItem : IBindingItem, IDisposable
{
- private delegate void PropertyChangedHandler(object previousValue, object newValue, bool shouldRaiseValueChanged);
+ private delegate void PropertyChangedHandler(object? previousValue, object? newValue, bool shouldRaiseValueChanged);
- private ManagedWeakReference _dataContextWeakStorage;
+ private ManagedWeakReference? _dataContextWeakStorage;
private readonly SerialDisposable _propertyChanged = new SerialDisposable();
private bool _disposed;
private readonly DependencyPropertyValuePrecedences? _precedence;
- private readonly object _fallbackValue;
+ private readonly object? _fallbackValue;
private readonly bool _allowPrivateMembers;
- private ValueGetterHandler _valueGetter;
- private ValueGetterHandler _precedenceSpecificGetter;
- private ValueGetterHandler _substituteValueGetter;
- private ValueSetterHandler _valueSetter;
- private ValueSetterHandler _localValueSetter;
- private ValueUnsetterHandler _valueUnsetter;
+ private ValueGetterHandler? _valueGetter;
+ private ValueGetterHandler? _precedenceSpecificGetter;
+ private ValueGetterHandler? _substituteValueGetter;
+ private ValueSetterHandler? _valueSetter;
+ private ValueSetterHandler? _localValueSetter;
+ private ValueUnsetterHandler? _valueUnsetter;
- private Type _dataContextType;
+ private Type? _dataContextType;
public BindingItem(BindingItem next, string property, object fallbackValue) :
this(next, property, fallbackValue, null, false)
{
}
- internal BindingItem(BindingItem next, string property, object fallbackValue, DependencyPropertyValuePrecedences? precedence, bool allowPrivateMembers)
+ internal BindingItem(BindingItem? next, string property, object? fallbackValue, DependencyPropertyValuePrecedences? precedence, bool allowPrivateMembers)
{
Next = next;
PropertyName = property;
@@ -458,7 +460,7 @@ internal BindingItem(BindingItem next, string property, object fallbackValue, De
_allowPrivateMembers = allowPrivateMembers;
}
- public object DataContext
+ public object? DataContext
{
get => _dataContextWeakStorage?.Target;
set
@@ -470,7 +472,7 @@ public object DataContext
// detrimental to the use of INPC events processing.
// In case of an INPC, the bindings engine must reevaluate the path completely from the raising point, regardless
// of the reference being changed.
- if(FeatureConfiguration.Binding.IgnoreINPCSameReferences && !DependencyObjectStore.AreDifferent(DataContext, value))
+ if (FeatureConfiguration.Binding.IgnoreINPCSameReferences && !DependencyObjectStore.AreDifferent(DataContext, value))
{
return;
}
@@ -481,7 +483,7 @@ public object DataContext
}
}
- internal void SetWeakDataContext(ManagedWeakReference weakDataContext, bool transferReferenceOwnership = false)
+ internal void SetWeakDataContext(ManagedWeakReference? weakDataContext, bool transferReferenceOwnership = false)
{
var previousStorage = _dataContextWeakStorage;
@@ -494,12 +496,12 @@ internal void SetWeakDataContext(ManagedWeakReference weakDataContext, bool tran
Uno.UI.DataBinding.WeakReferencePool.ReturnWeakReference(this, previousStorage);
}
- public BindingItem Next { get; }
+ public BindingItem? Next { get; }
public string PropertyName { get; }
- public IValueChangedListener ValueChangedListener { get; set; }
+ public IValueChangedListener? ValueChangedListener { get; set; }
- public object Value
+ public object? Value
{
get
{
@@ -515,10 +517,10 @@ public object Value
/// Sets the value using the
///
/// The value to set
- private void SetValue(object value)
+ private void SetValue(object? value)
{
BuildValueSetter();
- SetSourceValue(_valueSetter, value);
+ SetSourceValue(_valueSetter!, value);
}
///
@@ -528,16 +530,16 @@ private void SetValue(object value)
public void SetLocalValue(object value)
{
BuildLocalValueSetter();
- SetSourceValue(_localValueSetter, value);
+ SetSourceValue(_localValueSetter!, value);
}
- public Type PropertyType
+ public Type? PropertyType
{
get
{
if (DataContext != null)
{
- return BindingPropertyHelper.GetPropertyType(_dataContextType, PropertyName, _allowPrivateMembers);
+ return BindingPropertyHelper.GetPropertyType(_dataContextType!, PropertyName, _allowPrivateMembers);
}
else
{
@@ -546,18 +548,18 @@ public Type PropertyType
}
}
- internal object GetPrecedenceSpecificValue()
+ internal object? GetPrecedenceSpecificValue()
{
BuildPrecedenceSpecificValueGetter();
- return GetSourceValue(_precedenceSpecificGetter);
+ return GetSourceValue(_precedenceSpecificGetter!);
}
- internal object GetSubstituteValue()
+ internal object? GetSubstituteValue()
{
BuildSubstituteValueGetter();
- return GetSourceValue(_substituteValueGetter);
+ return GetSourceValue(_substituteValueGetter!);
}
private bool _isDataContextChanging;
@@ -628,7 +630,7 @@ private void OnDataContextChanged()
private void ClearCachedGetters()
{
- var currentType = DataContext.GetType();
+ var currentType = DataContext!.GetType();
if (_dataContextType != currentType && _dataContextType != null)
{
@@ -667,7 +669,7 @@ private void BuildLocalValueSetter()
}
}
- private void SetSourceValue(ValueSetterHandler setter, object value)
+ private void SetSourceValue(ValueSetterHandler setter, object? value)
{
// Capture the datacontext before the call to avoid a race condition with the GC.
var dataContext = DataContext;
@@ -720,14 +722,14 @@ private void BuildSubstituteValueGetter()
}
}
- private object GetSourceValue()
+ private object? GetSourceValue()
{
BuildValueGetter();
- return GetSourceValue(_valueGetter);
+ return GetSourceValue(_valueGetter!);
}
- private object GetSourceValue(ValueGetterHandler getter)
+ private object? GetSourceValue(ValueGetterHandler getter)
{
// Capture the datacontext before the call to avoid a race condition with the GC.
var dataContext = DataContext;
@@ -778,7 +780,7 @@ internal void ClearValue()
if (dataContext != null)
{
- _valueUnsetter(dataContext);
+ _valueUnsetter!(dataContext);
}
else
{
@@ -789,7 +791,7 @@ internal void ClearValue()
}
}
- private void RaiseValueChanged(object newValue)
+ private void RaiseValueChanged(object? newValue)
{
ValueChangedListener?.OnValueChanged(newValue);
}
@@ -806,9 +808,9 @@ private IDisposable SubscribeToPropertyChanged(PropertyChangedHandler action)
for (var i = 0; i < _propertyChangedHandlers.Count; i++)
{
var handler = _propertyChangedHandlers[i];
- object previousValue = default;
+ object? previousValue = default;
- Action updateProperty = () =>
+ Action? updateProperty = () =>
{
var newValue = GetSourceValue();
@@ -822,7 +824,7 @@ private IDisposable SubscribeToPropertyChanged(PropertyChangedHandler action)
action(previousValue, DependencyProperty.UnsetValue, shouldRaiseValueChanged: false);
};
- var handlerDisposable = handler(_dataContextWeakStorage, PropertyName, updateProperty);
+ var handlerDisposable = handler(_dataContextWeakStorage!, PropertyName, updateProperty);
if (handlerDisposable != null)
{