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) {