From 26baec3da09bb0c8bea613946d1243f4d8afd2b3 Mon Sep 17 00:00:00 2001 From: Yauheni Pakala Date: Wed, 23 Feb 2022 14:59:51 +0300 Subject: [PATCH] Enable nullability for Bindings primitives (#473) --- .../DroidBinding.cs | 115 ++- .../DroidBindingFactory.cs | 46 +- Softeq.XToolkit.Bindings.iOS/AppleBinding.cs | 122 +++- .../AppleBindingFactory.cs | 38 +- Softeq.XToolkit.Bindings/Binding.cs | 11 +- Softeq.XToolkit.Bindings/BindingExtensions.cs | 112 +-- .../BindingFactoryBase.cs | 28 +- Softeq.XToolkit.Bindings/BindingGeneric.cs | 687 ++++++++++-------- .../Extensions/BindableExtensions.cs | 45 +- Softeq.XToolkit.Bindings/IBindingFactory.cs | 41 +- ...rceTriggerMode.cs => UpdateTriggerMode.cs} | 8 +- 11 files changed, 693 insertions(+), 560 deletions(-) rename Softeq.XToolkit.Bindings/{UpdateSourceTriggerMode.cs => UpdateTriggerMode.cs} (84%) diff --git a/Softeq.XToolkit.Bindings.Droid/DroidBinding.cs b/Softeq.XToolkit.Bindings.Droid/DroidBinding.cs index d80728a16..8b6edbdf0 100644 --- a/Softeq.XToolkit.Bindings.Droid/DroidBinding.cs +++ b/Softeq.XToolkit.Bindings.Droid/DroidBinding.cs @@ -8,8 +8,6 @@ using Android.Views; using Android.Widget; -#nullable disable - namespace Softeq.XToolkit.Bindings.Droid { /// @@ -19,11 +17,11 @@ public class DroidBinding : Binding public DroidBinding( object source, string sourcePropertyName, - object target = null, - string targetPropertyName = null, + object? target = null, + string? targetPropertyName = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) : base(source, sourcePropertyName, target, targetPropertyName, mode, fallbackValue, targetNullValue) { } @@ -32,11 +30,11 @@ public DroidBinding( public DroidBinding( object source, Expression> sourcePropertyExpression, - object target = null, - Expression> targetPropertyExpression = null, + object? target = null, + Expression>? targetPropertyExpression = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) : base(source, sourcePropertyExpression, target, targetPropertyExpression, mode, fallbackValue, targetNullValue) { } @@ -46,11 +44,11 @@ public DroidBinding( object source, Expression> sourcePropertyExpression, bool? resolveTopField, - object target = null, - Expression> targetPropertyExpression = null, + object? target = null, + Expression>? targetPropertyExpression = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) : base( source, sourcePropertyExpression, @@ -64,20 +62,23 @@ public DroidBinding( } /// - /// Define when the binding should be evaluated when the bound source object - /// is a control. Because Xamarin controls are not DependencyObjects, the - /// bound property will not automatically update the binding attached to it. Instead, - /// use this method to define which of the control's events should be observed. + /// Define when the binding should be evaluated when the bound source object is a control. + /// + /// Because Xamarin controls are not DependencyObjects, + /// the bound property will not automatically update the binding attached to it. + /// + /// Instead, use this method to define which of the control's events should be observed. /// /// - /// Defines the binding's update mode. Use - /// to update the binding when - /// the source control loses the focus. You can also use - /// to update the binding + /// Defines the binding's update mode. + /// + /// Use to update the binding when the source control loses the focus. + /// You can also use to update the binding /// when the source control's property changes. + /// /// The PropertyChanged mode should only be used with the following items: - /// - an EditText control and its Text property (TextChanged event). - /// - a CompoundButton control and its Checked property (CheckedChange event). + /// - control and its Text property (TextChanged event). + /// - control and its Checked property (CheckedChange event). /// /// The Binding instance. /// @@ -87,60 +88,55 @@ public DroidBinding( /// public Binding ObserveSourceEvent(UpdateTriggerMode mode = UpdateTriggerMode.PropertyChanged) { - switch (mode) + return mode switch { - case UpdateTriggerMode.LostFocus: - return ObserveSourceEvent("FocusChange"); - - case UpdateTriggerMode.PropertyChanged: - return CheckControlSource(); - } - - return this; + UpdateTriggerMode.LostFocus => ObserveSourceEvent("FocusChange"), + UpdateTriggerMode.PropertyChanged => CheckControlSource(), + _ => this + }; } /// - /// Define when the binding should be evaluated when the bound target object - /// is a control. Because Xamarin controls are not DependencyObjects, the - /// bound property will not automatically update the binding attached to it. Instead, - /// use this method to define which of the control's events should be observed. + /// Define when the binding should be evaluated when the bound target object is a control. + /// + /// Because Xamarin controls are not DependencyObjects, + /// the bound property will not automatically update the binding attached to it. + /// + /// Instead, use this method to define which of the control's events should be observed. /// /// - /// Defines the binding's update mode. Use - /// to update the binding when - /// the source control loses the focus. You can also use - /// to update the binding - /// when the source control's property changes. + /// Defines the binding's update mode. + /// + /// Use to update the binding when the target control loses the focus. + /// You can also use to update the binding + /// when the target control's property changes. + /// /// The PropertyChanged mode should only be used with the following items: - /// - an EditText control and its Text property (TextChanged event). - /// - a CompoundButton control and its Checked property (CheckedChange event). + /// - control and its Text property (TextChanged event). + /// - control and its Checked property (CheckedChange event). /// /// The Binding instance. /// - /// When this method is called - /// on a OneTime or a OneWay binding. This exception can - /// also be thrown when the source object is null or has already been - /// garbage collected before this method is called. + /// When this method is called on a OneTime or a OneWay binding. + /// This exception can also be thrown when the source object is null or + /// has already been garbage collected before this method is called. /// public Binding ObserveTargetEvent(UpdateTriggerMode mode = UpdateTriggerMode.PropertyChanged) { - switch (mode) + return mode switch { - case UpdateTriggerMode.LostFocus: - return ObserveTargetEvent("FocusChange"); - - case UpdateTriggerMode.PropertyChanged: - return CheckControlTarget(); - } - - return this; + UpdateTriggerMode.LostFocus => ObserveTargetEvent("FocusChange"), + UpdateTriggerMode.PropertyChanged => CheckControlTarget(), + _ => this + }; } + /// [SuppressMessage("ReSharper", "RedundantAssignment", Justification = "Reviewed.")] [SuppressMessage("ReSharper", "EntityNameCapturedOnly.Local", Justification = "Reviewed.")] protected override Binding CheckControlSource() { - switch (PropertySource.Target) + switch (_propertySource!.Target) { case EditText textBox: { @@ -161,6 +157,7 @@ protected override Binding CheckControlSource() } } + /// [SuppressMessage("ReSharper", "RedundantAssignment", Justification = "Reviewed.")] [SuppressMessage("ReSharper", "EntityNameCapturedOnly.Local", Justification = "Reviewed.")] protected override Binding CheckControlTarget() @@ -170,7 +167,7 @@ protected override Binding CheckControlTarget() return this; } - switch (PropertyTarget.Target) + switch (_propertyTarget!.Target) { case EditText textBox: { diff --git a/Softeq.XToolkit.Bindings.Droid/DroidBindingFactory.cs b/Softeq.XToolkit.Bindings.Droid/DroidBindingFactory.cs index a1b645ef6..2459e3fa0 100644 --- a/Softeq.XToolkit.Bindings.Droid/DroidBindingFactory.cs +++ b/Softeq.XToolkit.Bindings.Droid/DroidBindingFactory.cs @@ -2,6 +2,7 @@ // http://www.softeq.com using System; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; using System.Windows.Input; @@ -9,10 +10,11 @@ using Android.Widget; using Softeq.XToolkit.Common.Threading; -#nullable disable - namespace Softeq.XToolkit.Bindings.Droid { + /// + /// The Android-specific factory to create bindings. + /// public class DroidBindingFactory : BindingFactoryBase { /// @@ -20,11 +22,11 @@ public override Binding CreateBinding( object source, Expression> sourcePropertyExpression, bool? resolveTopField, - object target = null, - Expression> targetPropertyExpression = null, + object? target = null, + Expression>? targetPropertyExpression = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) { return new DroidBinding( source, @@ -41,11 +43,11 @@ public override Binding CreateBinding( public override Binding CreateBinding( object source, Expression> sourcePropertyExpression, - object target = null, - Expression> targetPropertyExpression = null, + object? target = null, + Expression>? targetPropertyExpression = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) { return new DroidBinding( source, @@ -61,11 +63,11 @@ public override Binding CreateBinding( public override Binding CreateBinding( object source, string sourcePropertyName, - object target = null, - string targetPropertyName = null, + object? target = null, + string? targetPropertyName = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) { return new DroidBinding( source, @@ -77,12 +79,13 @@ public override Binding CreateBinding( targetNullValue); } + /// public override Delegate GetCommandHandler( EventInfo info, string eventName, Type elementType, ICommand command, - object commandParameter = null) + object? commandParameter = null) { if (string.IsNullOrEmpty(eventName) && elementType == typeof(CheckBox)) { @@ -98,18 +101,19 @@ public override Delegate GetCommandHandler( return base.GetCommandHandler(info, eventName, elementType, command, commandParameter); } + /// public override Delegate GetCommandHandler( EventInfo info, string eventName, Type elementType, ICommand command, - Binding castedBinding) + Binding? castedBinding) { if (string.IsNullOrEmpty(eventName) && elementType == typeof(CheckBox)) { return new EventHandler((s, args) => { - object param = castedBinding == null ? default : castedBinding.Value; + var param = castedBinding == null ? default : castedBinding.Value; if (command.CanExecute(param)) { @@ -121,7 +125,8 @@ public override Delegate GetCommandHandler( return base.GetCommandHandler(info, eventName, elementType, command, castedBinding); } - public override string GetDefaultEventNameForControl(Type type) + /// + public override string? GetDefaultEventNameForControl(Type type) { if (type == typeof(CheckBox) || typeof(CheckBox).IsAssignableFrom(type)) { @@ -141,10 +146,11 @@ public override string GetDefaultEventNameForControl(Type type) return null; } + /// public override void HandleCommandCanExecute( object element, ICommand command, - Binding commandParameterBinding) + Binding? commandParameterBinding) { if (element is View view) { @@ -155,7 +161,7 @@ public override void HandleCommandCanExecute( private static void HandleViewEnabled( View view, ICommand command, - Binding commandParameterBinding) + Binding? commandParameterBinding) { var commandParameter = commandParameterBinding == null ? default diff --git a/Softeq.XToolkit.Bindings.iOS/AppleBinding.cs b/Softeq.XToolkit.Bindings.iOS/AppleBinding.cs index a02c624d8..fe6a46494 100644 --- a/Softeq.XToolkit.Bindings.iOS/AppleBinding.cs +++ b/Softeq.XToolkit.Bindings.iOS/AppleBinding.cs @@ -6,8 +6,6 @@ using System.Linq.Expressions; using UIKit; -#nullable disable - namespace Softeq.XToolkit.Bindings.iOS { /// @@ -17,11 +15,11 @@ public class AppleBinding : Binding public AppleBinding( object source, string sourcePropertyName, - object target = null, - string targetPropertyName = null, + object? target = null, + string? targetPropertyName = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) : base( source, sourcePropertyName, @@ -37,11 +35,11 @@ public AppleBinding( public AppleBinding( object source, Expression> sourcePropertyExpression, - object target = null, - Expression> targetPropertyExpression = null, + object? target = null, + Expression>? targetPropertyExpression = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) : base( source, sourcePropertyExpression, @@ -58,11 +56,11 @@ public AppleBinding( object source, Expression> sourcePropertyExpression, bool? resolveTopField, - object target = null, - Expression> targetPropertyExpression = null, + object? target = null, + Expression>? targetPropertyExpression = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) : base( source, sourcePropertyExpression, @@ -75,43 +73,90 @@ public AppleBinding( { } + /// + /// Define when the binding should be evaluated when the bound source object is a control. + /// + /// Because Xamarin controls are not DependencyObjects, + /// the bound property will not automatically update the binding attached to it. + /// + /// Instead, use this method to define which of the control's events should be observed. + /// + /// + /// Defines the binding's update mode. + /// + /// Use to update the binding when the source control's property changes. + /// + /// The PropertyChanged mode should only be used with the following items: + /// - control and its Text property (TextChanged event). + /// - control and its Text property (EditingChanged event). + /// - control and its On property (ValueChanged event). + /// + /// The Binding instance. + /// + /// When this method is called on a OneTime binding. Such bindings cannot be updated. + /// This exception can also be thrown when the source object is null or has already been + /// garbage collected before this method is called. + /// + /// + /// When is , + /// because it only supported in Android at this time. + /// public Binding ObserveSourceEvent(UpdateTriggerMode mode = UpdateTriggerMode.PropertyChanged) { - switch (mode) + return mode switch { - case UpdateTriggerMode.LostFocus: - throw new ArgumentException( - "UpdateTriggerMode.LostFocus is only supported in Android at this time", - nameof(mode)); - - case UpdateTriggerMode.PropertyChanged: - return CheckControlSource(); - } - - return this; + UpdateTriggerMode.LostFocus => throw new ArgumentException( + "UpdateTriggerMode.LostFocus is only supported in Android at this time", nameof(mode)), + UpdateTriggerMode.PropertyChanged => CheckControlSource(), + _ => this + }; } + /// + /// Define when the binding should be evaluated when the bound target object is a control. + /// + /// Because Xamarin controls are not DependencyObjects, + /// the bound property will not automatically update the binding attached to it. + /// + /// Instead, use this method to define which of the control's events should be observed. + /// + /// + /// Defines the binding's update mode. + /// + /// Use to update the binding when the target control's property changes. + /// + /// The PropertyChanged mode should only be used with the following items: + /// - control and its Text property (TextChanged event). + /// - control and its Text property (EditingChanged event). + /// - control and its On property (ValueChanged event). + /// + /// The Binding instance. + /// + /// When this method is called on a OneTime or a OneWay binding. + /// This exception can also be thrown when the source object is null or + /// has already been garbage collected before this method is called. + /// + /// + /// When is , + /// because it only supported in Android at this time. + /// public Binding ObserveTargetEvent(UpdateTriggerMode mode) { - switch (mode) + return mode switch { - case UpdateTriggerMode.LostFocus: - throw new ArgumentException( - "UpdateTriggerMode.LostFocus is only supported in Android at this time", - nameof(mode)); - - case UpdateTriggerMode.PropertyChanged: - return CheckControlTarget(); - } - - return this; + UpdateTriggerMode.LostFocus => throw new ArgumentException( + "UpdateTriggerMode.LostFocus is only supported in Android at this time", nameof(mode)), + UpdateTriggerMode.PropertyChanged => CheckControlTarget(), + _ => this + }; } + /// [SuppressMessage("ReSharper", "RedundantAssignment", Justification = "Reviewed.")] [SuppressMessage("ReSharper", "EntityNameCapturedOnly.Local", Justification = "Reviewed.")] protected override Binding CheckControlSource() { - switch (PropertySource.Target) + switch (_propertySource!.Target) { case UITextView textBox: { @@ -139,6 +184,7 @@ protected override Binding CheckControlSource() } } + /// [SuppressMessage("ReSharper", "RedundantAssignment", Justification = "Reviewed.")] [SuppressMessage("ReSharper", "EntityNameCapturedOnly.Local", Justification = "Reviewed.")] protected override Binding CheckControlTarget() @@ -148,7 +194,7 @@ protected override Binding CheckControlTarget() return this; } - switch (PropertyTarget.Target) + switch (_propertyTarget!.Target) { case UITextView textBox: { diff --git a/Softeq.XToolkit.Bindings.iOS/AppleBindingFactory.cs b/Softeq.XToolkit.Bindings.iOS/AppleBindingFactory.cs index ad02c3fb5..d0700aeea 100644 --- a/Softeq.XToolkit.Bindings.iOS/AppleBindingFactory.cs +++ b/Softeq.XToolkit.Bindings.iOS/AppleBindingFactory.cs @@ -2,14 +2,16 @@ // http://www.softeq.com using System; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Windows.Input; using UIKit; -#nullable disable - namespace Softeq.XToolkit.Bindings.iOS { + /// + /// The iOS-specific factory to create bindings. + /// public class AppleBindingFactory : BindingFactoryBase { /// @@ -17,11 +19,11 @@ public override Binding CreateBinding( object source, Expression> sourcePropertyExpression, bool? resolveTopField, - object target = null, - Expression> targetPropertyExpression = null, + object? target = null, + Expression>? targetPropertyExpression = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) { return new AppleBinding( source, @@ -38,11 +40,11 @@ public override Binding CreateBinding( public override Binding CreateBinding( object source, Expression> sourcePropertyExpression, - object target = null, - Expression> targetPropertyExpression = null, + object? target = null, + Expression>? targetPropertyExpression = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) { return new AppleBinding( source, @@ -58,11 +60,11 @@ public override Binding CreateBinding( public override Binding CreateBinding( object source, string sourcePropertyName, - object target = null, - string targetPropertyName = null, + object? target = null, + string? targetPropertyName = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) { return new AppleBinding( source, @@ -74,7 +76,8 @@ public override Binding CreateBinding( targetNullValue); } - public override string GetDefaultEventNameForControl(Type type) + /// + public override string? GetDefaultEventNameForControl(Type type) { if (type == typeof(UIButton) || typeof(UIButton).IsAssignableFrom(type)) { @@ -94,10 +97,11 @@ public override string GetDefaultEventNameForControl(Type type) return null; } + /// public override void HandleCommandCanExecute( object element, ICommand command, - Binding commandParameterBinding) + Binding? commandParameterBinding) { if (element is UIControl control) { @@ -108,7 +112,7 @@ public override void HandleCommandCanExecute( private static void HandleControlEnabled( UIControl control, ICommand command, - Binding commandParameterBinding) + Binding? commandParameterBinding) { var commandParameter = commandParameterBinding == null ? default diff --git a/Softeq.XToolkit.Bindings/Binding.cs b/Softeq.XToolkit.Bindings/Binding.cs index 24852bb15..db2a146f4 100644 --- a/Softeq.XToolkit.Bindings/Binding.cs +++ b/Softeq.XToolkit.Bindings/Binding.cs @@ -21,7 +21,7 @@ public abstract class Binding protected WeakReference? TopTarget; /// - /// Occurs when the value of the databound property changes. + /// Occurs when the value of the data-bound property changes. /// public abstract event EventHandler ValueChanged; @@ -50,20 +50,17 @@ public abstract class Binding : TopTarget.Target; /// - /// Instructs the Binding instance to stop listening to value changes and to - /// remove all listeneners. + /// Instructs the Binding instance to stop listening to value changes and to remove all listeners. /// public abstract void Detach(); /// - /// Forces the Binding's value to be reevaluated. The target value will - /// be set to the source value. + /// Forces the Binding's value to be reevaluated. The target value will be set to the source value. /// public abstract void ForceUpdateValueFromSourceToTarget(); /// - /// Forces the Binding's value to be reevaluated. The source value will - /// be set to the target value. + /// Forces the Binding's value to be reevaluated. The source value will be set to the target value. /// public abstract void ForceUpdateValueFromTargetToSource(); } diff --git a/Softeq.XToolkit.Bindings/BindingExtensions.cs b/Softeq.XToolkit.Bindings/BindingExtensions.cs index 87f3d7646..1ed231d5d 100644 --- a/Softeq.XToolkit.Bindings/BindingExtensions.cs +++ b/Softeq.XToolkit.Bindings/BindingExtensions.cs @@ -2,14 +2,13 @@ // http://www.softeq.com using System; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; using System.Windows.Input; using Softeq.XToolkit.Common.Commands; using Softeq.XToolkit.Common.Disposables; -#nullable disable - namespace Softeq.XToolkit.Bindings { /// @@ -17,8 +16,19 @@ namespace Softeq.XToolkit.Bindings /// public static class BindingExtensions { - private static IBindingFactory _bindingFactory; + private static IBindingFactory? _bindingFactory; + + private static IBindingFactory BindingFactory + { + get => _bindingFactory + ?? throw new InvalidOperationException( + $"Please initialize extensions via {nameof(BindingExtensions)}.{nameof(Initialize)} before using."); + } + /// + /// Initializes extensions methods. Should be called at least once before using extension methods. + /// + /// The factory instance. public static void Initialize(IBindingFactory bindingFactory) { _bindingFactory = bindingFactory; @@ -42,8 +52,8 @@ public static void Initialize(IBindingFactory bindingFactory) /// and methods to configure the binding. /// It is very possible that TSource and TTarget are the same type in which case no conversion occurs. /// - /// The type of the property that is being databound before conversion. - /// The type of the property that is being databound after conversion. + /// The type of the property that is being data-bound before conversion. + /// The type of the property that is being data-bound after conversion. /// /// The source of the binding. If this object implements /// and the is OneWay or TwoWay, the target will be notified of changes @@ -80,16 +90,17 @@ public static void Initialize(IBindingFactory bindingFactory) /// /// The value used when the source property is null (or equals to default(TSource)). /// The new Binding instance. + /// When class is not initialized. public static Binding SetBinding( this object source, Expression> sourcePropertyExpression, object target, - Expression> targetPropertyExpression = null, + Expression>? targetPropertyExpression = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) { - return _bindingFactory.CreateBinding( + return BindingFactory.CreateBinding( source, sourcePropertyExpression, null, @@ -137,14 +148,15 @@ public static Binding SetBinding( /// The value used when the source property is null (or equals to default(TSource)). /// The type of the bound property. /// The created binding instance. + /// When class is not initialized. public static Binding SetBinding( this object source, Expression> sourcePropertyExpression, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) { - return _bindingFactory.CreateBinding( + return BindingFactory.CreateBinding( source, sourcePropertyExpression, true, @@ -165,9 +177,9 @@ public static Binding SetBinding( /// If the target implements , has observable properties and /// the is TwoWay, the source will also be notified of changes to the target's properties. /// - /// The type of the source property that is being databound. + /// The type of the source property that is being data-bound. /// - /// The type of the target property that is being databound. If the source type + /// The type of the target property that is being data-bound. If the source type /// is not the same as the target type, an automatic conversion will be attempted. However only /// simple types can be converted. For more complex conversions, use the /// @@ -204,15 +216,16 @@ public static Binding SetBinding( /// /// The value used when the source property is null (or equals to default(TSource)). /// The new Binding instance. + /// When class is not initialized. public static Binding SetBinding( this object source, Expression> sourcePropertyExpression, - Expression> targetPropertyExpression = null, + Expression>? targetPropertyExpression = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) { - return _bindingFactory.CreateBinding( + return BindingFactory.CreateBinding( source, sourcePropertyExpression, null, @@ -233,9 +246,9 @@ public static Binding SetBinding( /// raises the PropertyChanged event and the is TwoWay, /// the source property will also be synchronized with the target property. /// - /// The type of the source property that is being databound. + /// The type of the source property that is being data-bound. /// - /// The type of the target property that is being databound. + /// The type of the target property that is being data-bound. /// /// If the source type is not the same as the target type, an automatic conversion will be attempted. /// However only simple types can be converted. For more complex conversions, @@ -269,16 +282,17 @@ public static Binding SetBinding( /// /// The value used when the source property is null (or equals to default(TSource)). /// The new Binding instance. + /// When class is not initialized. public static Binding SetBinding( this object source, string sourcePropertyName, object target, - string targetPropertyName = null, + string? targetPropertyName = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) { - return _bindingFactory.CreateBinding( + return BindingFactory.CreateBinding( source, sourcePropertyName, target, @@ -297,9 +311,9 @@ public static Binding SetBinding( /// If the target implements , has observable properties and /// the is TwoWay, the source will also be notified of changes to the target's properties. /// - /// The type of the source property that is being databound. + /// The type of the source property that is being data-bound. /// - /// The type of the target property that is being databound. If the source type is not the same as the target type, + /// The type of the target property that is being data-bound. If the source type is not the same as the target type, /// an automatic conversion will be attempted. However only simple types can be converted. /// For more complex conversions, use the /// and methods to define custom converters. @@ -327,15 +341,16 @@ public static Binding SetBinding( /// /// The value used when the source property is null (or equals to default(TSource)). /// The new Binding instance. + /// When class is not initialized. public static Binding SetBinding( this object source, string sourcePropertyName, - string targetPropertyName = null, + string? targetPropertyName = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) { - return _bindingFactory.CreateBinding( + return BindingFactory.CreateBinding( source, sourcePropertyName, null, @@ -352,6 +367,7 @@ public static Binding SetBinding( /// The element to which the command is added. /// The name of the event that will be subscribed to to actuate the command. /// The command that must be added to the element. + /// When class is not initialized. public static void SetCommand( this object element, string eventName, @@ -360,7 +376,7 @@ public static void SetCommand( var t = element.GetType(); var e = t.GetEventInfoForControl(eventName); - var handler = _bindingFactory.GetCommandHandler(e, eventName, t, command); + var handler = BindingFactory.GetCommandHandler(e, eventName, t, command); e.AddEventHandler(element, handler); @@ -380,6 +396,7 @@ public static void SetCommand( /// is executed. This is a fixed value. To pass an observable value, use one of the SetCommand /// overloads that uses a Binding as CommandParameter. /// + /// When class is not initialized. public static void SetCommand( this object element, string eventName, @@ -389,7 +406,7 @@ public static void SetCommand( var t = element.GetType(); var e = t.GetEventInfoForControl(eventName); - var handler = _bindingFactory.GetCommandHandler(e, eventName, t, command, commandParameter); + var handler = BindingFactory.GetCommandHandler(e, eventName, t, command, commandParameter); e.AddEventHandler(element, handler); @@ -410,18 +427,19 @@ public static void SetCommand( /// Depending on the , the CommandParameter will be observed and changes /// will be passed to the command, for example to update the CanExecute. /// + /// When class is not initialized. public static void SetCommand( this object element, string eventName, ICommand command, - Binding commandParameterBinding) + Binding? commandParameterBinding) { var t = element.GetType(); var e = t.GetEventInfoForControl(eventName); - var castedBinding = (Binding) commandParameterBinding; + var castedBinding = commandParameterBinding as Binding; - var handler = _bindingFactory.GetCommandHandler(e, eventName, t, command, castedBinding); + var handler = BindingFactory.GetCommandHandler(e, eventName, t, command, castedBinding); e.AddEventHandler(element, handler); @@ -441,6 +459,7 @@ public static void SetCommand( /// The element to which the command is added. /// The name of the event that will be subscribed to to actuate the command. /// The command that must be added to the element. + /// When class is not initialized. public static void SetCommand( this object element, string eventName, @@ -449,7 +468,7 @@ public static void SetCommand( var t = element.GetType(); var e = t.GetEventInfoForControl(eventName); - var handler = _bindingFactory.GetCommandHandler(e, eventName, t, command); + var handler = BindingFactory.GetCommandHandler(e, eventName, t, command); e.AddEventHandler(element, handler); @@ -470,6 +489,7 @@ public static void SetCommand( /// is executed. This is a fixed value. To pass an observable value, use one of the SetCommand /// overloads that uses a Binding as CommandParameter. /// + /// When class is not initialized. public static void SetCommand( this object element, string eventName, @@ -479,7 +499,7 @@ public static void SetCommand( var t = element.GetType(); var e = t.GetEventInfoForControl(eventName); - var handler = _bindingFactory.GetCommandHandler(e, eventName, t, command, commandParameter); + var handler = BindingFactory.GetCommandHandler(e, eventName, t, command, commandParameter); e.AddEventHandler(element, handler); @@ -500,18 +520,19 @@ public static void SetCommand( /// that will passed to the . Depending on the Binding, the CommandParameter will be observed /// and changes will be passed to the command, for example to update the CanExecute. /// + /// When class is not initialized. public static void SetCommand( this object element, string eventName, ICommand command, - Binding commandParameterBinding) + Binding? commandParameterBinding) { var t = element.GetType(); var e = t.GetEventInfoForControl(eventName); - var castedBinding = (Binding) commandParameterBinding; + var castedBinding = commandParameterBinding as Binding; - var handler = _bindingFactory.GetCommandHandler(e, eventName, t, command, castedBinding); + var handler = BindingFactory.GetCommandHandler(e, eventName, t, command, castedBinding); e.AddEventHandler(element, handler); @@ -531,6 +552,7 @@ public static void SetCommand( /// The name of the event that will be subscribed to to actuate the command. /// The command that must be added to the element. /// instance for manual unset/unsubscribe of command. + /// When class is not initialized. public static IDisposable SetCommandWithDisposing( this object element, string eventName, @@ -539,7 +561,7 @@ public static IDisposable SetCommandWithDisposing( var t = element.GetType(); var e = t.GetEventInfoForControl(eventName); - var handler = _bindingFactory.GetCommandHandler(e, eventName, t, command); + var handler = BindingFactory.GetCommandHandler(e, eventName, t, command); e.AddEventHandler(element, handler); @@ -615,11 +637,11 @@ public static void SetCommand( SetCommand(element, eventName, new RelayCommand(action)); } - internal static EventInfo GetEventInfoForControl(this Type type, string eventName) + internal static EventInfo GetEventInfoForControl(this Type type, string? eventName) { if (string.IsNullOrEmpty(eventName)) { - eventName = _bindingFactory.GetDefaultEventNameForControl(type); + eventName = BindingFactory.GetDefaultEventNameForControl(type); } if (string.IsNullOrEmpty(eventName)) @@ -647,9 +669,9 @@ private static void HandleCommandCanExecute( private static void HandleCommandCanExecute( object element, ICommand command, - Binding commandParameterBinding = null) + Binding? commandParameterBinding = null) { - _bindingFactory.HandleCommandCanExecute(element, command, commandParameterBinding); + BindingFactory.HandleCommandCanExecute(element, command, commandParameterBinding); } } } diff --git a/Softeq.XToolkit.Bindings/BindingFactoryBase.cs b/Softeq.XToolkit.Bindings/BindingFactoryBase.cs index 1b66ce368..d686f50eb 100644 --- a/Softeq.XToolkit.Bindings/BindingFactoryBase.cs +++ b/Softeq.XToolkit.Bindings/BindingFactoryBase.cs @@ -2,12 +2,16 @@ // http://www.softeq.com using System; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; using System.Windows.Input; namespace Softeq.XToolkit.Bindings { + /// + /// The base factory to create bindings. + /// public abstract class BindingFactoryBase : IBindingFactory { /// @@ -18,8 +22,8 @@ public abstract Binding CreateBinding( object? target = null, Expression>? targetPropertyExpression = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default); + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!); /// public abstract Binding CreateBinding( @@ -28,8 +32,8 @@ public abstract Binding CreateBinding( object? target = null, Expression>? targetPropertyExpression = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default); + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!); /// public abstract Binding CreateBinding( @@ -38,17 +42,17 @@ public abstract Binding CreateBinding( object? target = null, string? targetPropertyName = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default); + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!); /// - public abstract string GetDefaultEventNameForControl(Type type); + public abstract string? GetDefaultEventNameForControl(Type type); /// public abstract void HandleCommandCanExecute( object element, ICommand command, - Binding commandParameterBinding); + Binding? commandParameterBinding); /// public virtual Delegate GetCommandHandler( @@ -74,11 +78,11 @@ public virtual Delegate GetCommandHandler( string eventName, Type elementType, ICommand command, - Binding castedBinding) + Binding? castedBinding) { EventHandler handler = (_, __) => { - object param = (castedBinding == null ? default : castedBinding.Value)!; + var param = castedBinding == null ? default : castedBinding.Value; if (command.CanExecute(param)) { @@ -129,11 +133,11 @@ public virtual Delegate GetCommandHandler( string eventName, Type elementType, ICommand command, - Binding castedBinding) + Binding? castedBinding) { EventHandler handler = (_, __) => { - object param = (castedBinding == null ? default : castedBinding.Value)!; + var param = castedBinding == null ? default : castedBinding.Value; if (command.CanExecute(param)) { diff --git a/Softeq.XToolkit.Bindings/BindingGeneric.cs b/Softeq.XToolkit.Bindings/BindingGeneric.cs index 043bd5d8c..a4ee7f206 100644 --- a/Softeq.XToolkit.Bindings/BindingGeneric.cs +++ b/Softeq.XToolkit.Bindings/BindingGeneric.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -11,51 +12,75 @@ using Softeq.XToolkit.Common.Converters; using Softeq.XToolkit.Common.Weak; -#nullable disable - -#pragma warning disable SA1649, SA1201, SA1204 - namespace Softeq.XToolkit.Bindings { /// /// Creates a binding between two properties. /// - /// If the source implements , the source property raises the PropertyChanged event and - /// the is OneWay or TwoWay, the target property will be synchronized with the source property. + /// If the source implements , + /// the source property raises the PropertyChanged event and the is OneWay or TwoWay, + /// the target property will be synchronized with the source property. /// - /// If the target implements , the target property raises the PropertyChanged event - /// and the is TwoWay, the source property will also be synchronized with the target property. + /// If the target implements , + /// the target property raises the PropertyChanged event and the is TwoWay, + /// the source property will also be synchronized with the target property. /// - /// The type of the source property that is being databound. + /// The type of the source property that is being data-bound. /// - /// The type of the target property that is being databound. If the source type + /// The type of the target property that is being data-bound. If the source type /// is not the same as the target type, an automatic conversion will be attempted. However only /// simple types can be converted. For more complex conversions, use the /// and methods to define custom converters. /// +#pragma warning disable SA1649 public abstract class Binding : Binding +#pragma warning restore SA1649 { - private readonly SimpleConverter _converter = new SimpleConverter(); + private readonly WeakConverter _converter = new WeakConverter(); private readonly List _listeners = new List(); - private readonly Expression> _sourcePropertyExpression; - private readonly Func _sourcePropertyFunc; - private readonly string _sourcePropertyName; - private readonly Expression> _targetPropertyExpression; - private readonly string _targetPropertyName; + + private readonly string? _sourcePropertyName; + private readonly string? _targetPropertyName; + + private readonly Expression>? _sourcePropertyExpression; + private readonly Expression>? _targetPropertyExpression; + + private readonly Func? _sourcePropertyFunc; + + /// + /// The source property handlers. + /// public readonly Dictionary SourceHandlers = new Dictionary(); + + /// + /// The target property handlers. + /// public readonly Dictionary TargetHandlers = new Dictionary(); + private bool _isFallbackValueActive; - private WeakAction _onSourceUpdate; - private WeakAction _onSourceUpdateWithParameter; private bool _resolveTopField; + private bool _settingSourceToTarget; private bool _settingTargetToSource; - private PropertyInfo _sourceProperty; - private WeakFunc _sourceUpdateFunctionWithParameter; - private PropertyInfo _targetProperty; - private IConverter _valueConverter; - protected WeakReference PropertySource; - protected WeakReference PropertyTarget; + + private PropertyInfo? _sourceProperty; + private PropertyInfo? _targetProperty; + + private WeakAction? _onSourceUpdate; + private WeakAction? _onSourceUpdateWithParameter; + private WeakFunc? _onSourceUpdateFunctionWithParameter; + + private IConverter? _valueConverter; + + /// + /// A weak reference to the source property. + /// + protected WeakReference? _propertySource; + + /// + /// A weak reference to the target property. + /// + protected WeakReference? _propertyTarget; /// /// Initializes a new instance of the class for @@ -73,47 +98,46 @@ public abstract class Binding : Binding /// The name of the target property for the binding. /// /// The mode of the binding. OneTime means that the target property will be set once (when the binding is - /// created) but that subsequent changes will be ignored. OneWay means that the target property will be set, and - /// if the PropertyChanged event is raised by the source, the target property will be updated. TwoWay means that the - /// source - /// property will also be updated if the target raises the PropertyChanged event. Default means OneWay if only the - /// source - /// implements INPC, and TwoWay if both the source and the target implement INPC. + /// created) but that subsequent changes will be ignored. OneWay means that the target property will be set, + /// and if the PropertyChanged event is raised by the source, the target property will be updated. + /// TwoWay means that the source property will also be updated if the target raises the PropertyChanged event. + /// Default means OneWay if only the source implements INPC, + /// and TwoWay if both the source and the target implement INPC. /// /// - /// Tthe value to use when the binding is unable to return a value. This can happen if one of the + /// The value to use when the binding is unable to return a value. This can happen if one of the /// items on the Path (except the source property itself) is null, or if the Converter throws an exception. /// /// - /// The value to use when the binding is unable to return a value. This can happen if one of the + /// The target value to use when the binding is unable to return a value. This can happen if one of the /// items on the Path (except the source property itself) is null, or if the Converter throws an exception. /// protected Binding( object source, string sourcePropertyName, - object target = null, - string targetPropertyName = null, + object? target = null, + string? targetPropertyName = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) { Mode = mode; FallbackValue = fallbackValue; TargetNullValue = targetNullValue; TopSource = new WeakReference(source); - PropertySource = new WeakReference(source); + _propertySource = new WeakReference(source); _sourcePropertyName = sourcePropertyName; if (target == null) { TopTarget = TopSource; - PropertyTarget = PropertySource; + _propertyTarget = _propertySource; } else { TopTarget = new WeakReference(target); - PropertyTarget = new WeakReference(target); + _propertyTarget = new WeakReference(target); } _targetPropertyName = targetPropertyName; @@ -121,7 +145,7 @@ protected Binding( } /// - /// Initializes a new instance of the class for + /// Initializes a new instance of the class for /// which the source and target properties are located in different objects. /// /// @@ -157,17 +181,15 @@ protected Binding( /// The value to use when the binding is unable to return a value. This can happen if one of the /// items on the Path (except the source property itself) is null, or if the Converter throws an exception. /// - /// - /// The value to use when the binding is unable to return a value. - /// + /// The value to use when the binding is unable to return a value. protected Binding( object source, Expression> sourcePropertyExpression, - object target = null, - Expression> targetPropertyExpression = null, + object? target = null, + Expression>? targetPropertyExpression = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) : this( source, sourcePropertyExpression, @@ -180,15 +202,16 @@ protected Binding( { } + /// protected Binding( object source, Expression> sourcePropertyExpression, bool? resolveTopField, - object target = null, - Expression> targetPropertyExpression = null, + object? target = null, + Expression>? targetPropertyExpression = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default, - TSource targetNullValue = default) + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!) { Mode = mode; FallbackValue = fallbackValue; @@ -210,6 +233,11 @@ protected Binding( resolveTopField ?? target == null && targetPropertyExpression != null); } + /// + /// Occurs when the value of the data-bound property changes. + /// + public override event EventHandler? ValueChanged; + /// /// Gets the value to use when the binding is unable to return a value. This can happen if one of the /// items on the Path (except the source property itself) is null, or if the Converter throws an exception. @@ -224,31 +252,30 @@ protected Binding( /// /// Gets the current value of the binding. /// + [MaybeNull] public TTarget Value { get { - if (PropertySource == null - || !PropertySource.IsAlive) + if (!(_propertySource is { IsAlive: true })) { return default; } - var type = PropertySource.Target.GetType(); + var type = _propertySource.Target.GetType(); var property = type.GetRuntimeProperty(_sourcePropertyName); - return (TTarget) property.GetValue(PropertySource.Target, null); + return (TTarget) property.GetValue(_propertySource.Target, null); } } /// - /// Defines a custom conversion method for a binding. To be used when the - /// binding's source property is of a different type than the binding's - /// target property, and the conversion cannot be done automatically (simple - /// values). + /// Defines a custom conversion method for a binding. + /// + /// To be used when the binding's source property is of a different type than the binding's + /// target property, and the conversion cannot be done automatically (simple values). /// /// - /// A func that will be called with the source - /// property's value, and will return the target property's value. + /// A func that will be called with the source property's value, and will return the target property's value. /// IMPORTANT: Note that closures are not supported at the moment /// due to the use of WeakActions (see http://stackoverflow.com/questions/25730530/). /// @@ -261,14 +288,13 @@ public Binding ConvertSourceToTarget(Func co } /// - /// Defines a custom conversion method for a two-way binding. To be used when the - /// binding's target property is of a different type than the binding's - /// source property, and the conversion cannot be done automatically (simple - /// values). + /// Defines a custom conversion method for a two-way binding. + /// + /// To be used when the binding's target property is of a different type than the binding's + /// source property, and the conversion cannot be done automatically (simple values). /// /// - /// A func that will be called with the source - /// property's value, and will return the target property's value. + /// A func that will be called with the source property's value, and will return the target property's value. /// IMPORTANT: Note that closures are not supported at the moment /// due to the use of WeakActions (see http://stackoverflow.com/questions/25730530/). /// @@ -280,7 +306,23 @@ public Binding ConvertTargetToSource(Func co return this; } - public Binding SetConverter(IConverter converter) + /// + /// Defines a custom conversion methods for a binding. + /// + /// To be used when the binding's source/target property is of a different type than the binding's + /// target/source property, and the conversion cannot be done automatically (simple values). + /// + /// + /// The instance of the converter, where: + /// + /// - will have behavior like method. + /// + /// + /// - will have behavior like method. + /// + /// + /// The Binding instance. + public Binding SetConverter(IConverter? converter) { if (converter == null) { @@ -307,8 +349,8 @@ public override void Detach() _listeners.Clear(); - DetachSourceHandlers(); - DetachTargetHandlers(); + DetachAllSourceHandlers(); + DetachAllTargetHandlers(); } /// @@ -318,9 +360,9 @@ public override void ForceUpdateValueFromSourceToTarget() { if (_onSourceUpdate == null && _onSourceUpdateWithParameter == null - && (PropertySource == null - || !PropertySource.IsAlive - || PropertySource.Target == null)) + && (_propertySource == null + || !_propertySource.IsAlive + || _propertySource.Target == null)) { return; } @@ -330,12 +372,12 @@ public override void ForceUpdateValueFromSourceToTarget() try { var value = GetSourceValue(); - var targetValue = _targetProperty.GetValue(PropertyTarget.Target); + var targetValue = _targetProperty.GetValue(_propertyTarget!.Target); if (!Equals(value, targetValue)) { _settingSourceToTarget = true; - SetTargetValue(value); + SetTargetValue(value!); _settingSourceToTarget = false; } } @@ -344,22 +386,20 @@ public override void ForceUpdateValueFromSourceToTarget() if (!Equals(FallbackValue, default(TSource))) { _settingSourceToTarget = true; - _targetProperty.SetValue(PropertyTarget.Target, FallbackValue, null); + _targetProperty.SetValue(_propertyTarget!.Target, FallbackValue, null); _settingSourceToTarget = false; } } } - if (_onSourceUpdate != null - && _onSourceUpdate.IsAlive) + if (_onSourceUpdate is { IsAlive: true }) { _onSourceUpdate.Execute(); } - if (_onSourceUpdateWithParameter != null - && _onSourceUpdateWithParameter.IsAlive) + if (_onSourceUpdateWithParameter is { IsAlive: true }) { - _onSourceUpdateWithParameter.Execute(_sourcePropertyFunc.Invoke()); + _onSourceUpdateWithParameter.Execute(_sourcePropertyFunc!.Invoke()); } RaiseValueChanged(); @@ -370,12 +410,12 @@ public override void ForceUpdateValueFromSourceToTarget() /// public override void ForceUpdateValueFromTargetToSource() { - if (PropertyTarget == null - || !PropertyTarget.IsAlive - || PropertyTarget.Target == null - || PropertySource == null - || !PropertySource.IsAlive - || PropertySource.Target == null) + if (_propertyTarget == null + || !_propertyTarget.IsAlive + || _propertyTarget.Target == null + || _propertySource == null + || !_propertySource.IsAlive + || _propertySource.Target == null) { return; } @@ -383,12 +423,12 @@ public override void ForceUpdateValueFromTargetToSource() if (_targetProperty != null) { var value = GetTargetValue(); - var sourceValue = _sourceProperty.GetValue(PropertySource.Target); + var sourceValue = _sourceProperty!.GetValue(_propertySource.Target); if (!Equals(value, sourceValue)) { _settingTargetToSource = true; - SetSourceValue(value); + SetSourceValue(value!); _settingTargetToSource = false; } } @@ -397,10 +437,12 @@ public override void ForceUpdateValueFromTargetToSource() } /// - /// Define when the binding should be evaluated when the bound source object - /// is a control. Because Xamarin controls are not DependencyObjects, the - /// bound property will not automatically update the binding attached to it. Instead, - /// use this method to define which of the control's events should be observed. + /// Define when the binding should be evaluated when the bound source object is a control. + /// + /// Because Xamarin controls are not DependencyObjects, + /// the bound property will not automatically update the binding attached to it. + /// + /// Instead, use this method to define which of the control's events should be observed. /// /// /// The name of the event that should be observed to update the binding's value. @@ -431,9 +473,9 @@ public Binding ObserveSourceEvent(string eventName) if (_onSourceUpdate == null && _onSourceUpdateWithParameter == null - && (PropertySource == null - || !PropertySource.IsAlive - || PropertySource.Target == null)) + && (_propertySource == null + || !_propertySource.IsAlive + || _propertySource.Target == null)) { throw new InvalidOperationException("Source is not ready"); } @@ -443,20 +485,19 @@ public Binding ObserveSourceEvent(string eventName) throw new ArgumentNullException(nameof(eventName)); } - var type = PropertySource.Target.GetType(); - var ev = type.GetRuntimeEvent(eventName); - if (ev == null) + var type = _propertySource!.Target.GetType(); + var @event = type.GetRuntimeEvent(eventName); + if (@event == null) { - throw new ArgumentException("Event not found: " + eventName, nameof(eventName)); + throw new ArgumentException($"Event not found: {eventName}", nameof(eventName)); } EventHandler handler = HandleSourceEvent; - var defaultHandlerInfo = SourceHandlers.Values.FirstOrDefault(i => i.IsDefault); - + var defaultHandlerInfo = SourceHandlers.Values.FirstOrDefault(x => x.IsDefault); if (defaultHandlerInfo != null) { - DetachSourceHandlers(); + DetachAllSourceHandlers(); } var info = new DelegateInfo @@ -473,34 +514,28 @@ public Binding ObserveSourceEvent(string eventName) SourceHandlers.Add(eventName, info); } - ev.AddEventHandler( - PropertySource.Target, - handler); + @event.AddEventHandler(_propertySource.Target, handler); return this; } /// - /// Define when the binding should be evaluated when the bound source object - /// is a control. Because Xamarin controls are not DependencyObjects, the - /// bound property will not automatically update the binding attached to it. Instead, - /// use this method to define which of the control's events should be observed. + /// Define when the binding should be evaluated when the bound source object is a control. + /// + /// Because Xamarin controls are not DependencyObjects, + /// the bound property will not automatically update the binding attached to it. + /// + /// Instead, use this method to define which of the control's events should be observed. /// /// - /// Use this method when the event requires a specific EventArgs type - /// instead of the standard EventHandler. + /// Use this method when the event requires a specific EventArgs type instead of the standard EventHandler. /// /// The type of the EventArgs used by this control's event. - /// - /// The name of the event that should be observed - /// to update the binding's value. - /// + /// The name of the event that should be observed to update the binding's value. /// The Binding instance. /// - /// When this method is called - /// on a OneTime binding. Such bindings cannot be updated. This exception can - /// also be thrown when the source object is null or has already been - /// garbage collected before this method is called. + /// When this method is called on a OneTime binding. Such bindings cannot be updated. This exception can + /// also be thrown when the source object is null or has already been garbage collected before this method is called. /// /// /// When the eventName parameter is null or is an empty string. @@ -524,9 +559,9 @@ public Binding ObserveSourceEvent(string eventName if (_onSourceUpdate == null && _onSourceUpdateWithParameter == null - && (PropertySource == null - || !PropertySource.IsAlive - || PropertySource.Target == null)) + && (_propertySource == null + || !_propertySource.IsAlive + || _propertySource.Target == null)) { throw new InvalidOperationException("Source is not ready"); } @@ -536,20 +571,19 @@ public Binding ObserveSourceEvent(string eventName throw new ArgumentNullException(nameof(eventName)); } - var type = PropertySource.Target.GetType(); - var ev = type.GetRuntimeEvent(eventName); - if (ev == null) + var type = _propertySource!.Target.GetType(); + var @event = type.GetRuntimeEvent(eventName); + if (@event == null) { - throw new ArgumentException("Event not found: " + eventName, nameof(eventName)); + throw new ArgumentException($"Event not found: {eventName}", nameof(eventName)); } EventHandler handler = HandleSourceEvent; - var defaultHandlerInfo = SourceHandlers.Values.FirstOrDefault(i => i.IsDefault); - + var defaultHandlerInfo = SourceHandlers.Values.FirstOrDefault(x => x.IsDefault); if (defaultHandlerInfo != null) { - DetachSourceHandlers(); + DetachAllSourceHandlers(); } var info = new DelegateInfo @@ -566,29 +600,24 @@ public Binding ObserveSourceEvent(string eventName SourceHandlers.Add(eventName, info); } - ev.AddEventHandler( - PropertySource.Target, - handler); + @event.AddEventHandler(_propertySource.Target, handler); return this; } /// - /// Define when the binding should be evaluated when the bound target object - /// is a control. Because Xamarin controls are not DependencyObjects, the - /// bound property will not automatically update the binding attached to it. Instead, - /// use this method to define which of the control's events should be observed. + /// Define when the binding should be evaluated when the bound target object is a control. + /// + /// Because Xamarin controls are not DependencyObjects, + /// the bound property will not automatically update the binding attached to it. + /// + /// Instead, use this method to define which of the control's events should be observed. /// - /// - /// The name of the event that should be observed - /// to update the binding's value. - /// + /// The name of the event that should be observed to update the binding's value. /// The Binding instance. /// - /// When this method is called - /// on a OneTime or a OneWay binding. This exception can - /// also be thrown when the source object is null or has already been - /// garbage collected before this method is called. + /// When this method is called on a OneTime or a OneWay binding. This exception can + /// also be thrown when the source object is null or has already been garbage collected before this method is called. /// /// /// When the eventName parameter is null or is an empty string. @@ -619,9 +648,9 @@ public Binding ObserveTargetEvent(string eventName) throw new InvalidOperationException("Cannot use SetTargetEvent with onSourceUpdate"); } - if (PropertyTarget == null - || !PropertyTarget.IsAlive - || PropertyTarget.Target == null) + if (_propertyTarget == null + || !_propertyTarget.IsAlive + || _propertyTarget.Target == null) { throw new InvalidOperationException("Target is not ready"); } @@ -631,21 +660,20 @@ public Binding ObserveTargetEvent(string eventName) throw new ArgumentNullException(nameof(eventName)); } - var type = PropertyTarget.Target.GetType(); + var type = _propertyTarget.Target.GetType(); - var ev = type.GetRuntimeEvent(eventName); - if (ev == null) + var @event = type.GetRuntimeEvent(eventName); + if (@event == null) { - throw new ArgumentException("Event not found: " + eventName, nameof(eventName)); + throw new ArgumentException($"Event not found: {eventName}", nameof(eventName)); } EventHandler handler = HandleTargetEvent; - var defaultHandlerInfo = TargetHandlers.Values.FirstOrDefault(i => i.IsDefault); - + var defaultHandlerInfo = TargetHandlers.Values.FirstOrDefault(x => x.IsDefault); if (defaultHandlerInfo != null) { - DetachTargetHandlers(); + DetachAllTargetHandlers(); } var info = new DelegateInfo @@ -662,34 +690,28 @@ public Binding ObserveTargetEvent(string eventName) TargetHandlers.Add(eventName, info); } - ev.AddEventHandler( - PropertyTarget.Target, - handler); + @event.AddEventHandler(_propertyTarget.Target, handler); return this; } /// - /// Define when the binding should be evaluated when the bound target object - /// is a control. Because Xamarin controls are not DependencyObjects, the - /// bound property will not automatically update the binding attached to it. Instead, - /// use this method to define which of the control's events should be observed. + /// Define when the binding should be evaluated when the bound target object is a control. + /// + /// Because Xamarin controls are not DependencyObjects, + /// the bound property will not automatically update the binding attached to it. + /// + /// Instead, use this method to define which of the control's events should be observed. /// /// - /// Use this method when the event requires a specific EventArgs type - /// instead of the standard EventHandler. + /// Use this method when the event requires a specific EventArgs type instead of the standard EventHandler. /// /// The type of the EventArgs used by this control's event. - /// - /// The name of the event that should be observed - /// to update the binding's value. - /// + /// The name of the event that should be observed to update the binding's value. /// The Binding instance. /// - /// When this method is called - /// on a OneTime or OneWay binding. This exception can - /// also be thrown when the target object is null or has already been - /// garbage collected before this method is called. + /// When this method is called on a OneTime or OneWay binding. This exception can + /// also be thrown when the target object is null or has already been garbage collected before this method is called. /// /// /// When the eventName parameter is null or is an empty string. @@ -721,9 +743,9 @@ public Binding ObserveTargetEvent(string eventName throw new InvalidOperationException("Cannot use SetTargetEvent with onSourceUpdate"); } - if (PropertyTarget == null - || !PropertyTarget.IsAlive - || PropertyTarget.Target == null) + if (_propertyTarget == null + || !_propertyTarget.IsAlive + || _propertyTarget.Target == null) { throw new InvalidOperationException("Target is not ready"); } @@ -733,21 +755,20 @@ public Binding ObserveTargetEvent(string eventName throw new ArgumentNullException(nameof(eventName)); } - var type = PropertyTarget.Target.GetType(); + var type = _propertyTarget.Target.GetType(); - var ev = type.GetRuntimeEvent(eventName); - if (ev == null) + var @event = type.GetRuntimeEvent(eventName); + if (@event == null) { - throw new ArgumentException("Event not found: " + eventName, nameof(eventName)); + throw new ArgumentException($"Event not found: {eventName}", nameof(eventName)); } EventHandler handler = HandleTargetEvent; - var defaultHandlerInfo = TargetHandlers.Values.FirstOrDefault(i => i.IsDefault); - + var defaultHandlerInfo = TargetHandlers.Values.FirstOrDefault(x => x.IsDefault); if (defaultHandlerInfo != null) { - DetachTargetHandlers(); + DetachAllTargetHandlers(); } var info = new DelegateInfo @@ -764,9 +785,7 @@ public Binding ObserveTargetEvent(string eventName TargetHandlers.Add(eventName, info); } - ev.AddEventHandler( - PropertyTarget.Target, - handler); + @event.AddEventHandler(_propertyTarget.Target, handler); return this; } @@ -801,6 +820,18 @@ public Binding WhenSourceChanges(Action callback) return this; } + /// + /// Defines an generic action that will be executed every time that the binding value changes. + /// + /// + /// The generic action that will be executed when the binding changes. + /// IMPORTANT: Note that closures are not supported at the moment + /// due to the use of WeakActions (see http://stackoverflow.com/questions/25730530/). + /// + /// The Binding instance. + /// + /// When the method is called on a binding that already has a target property set. + /// public Binding WhenSourceChanges(Action callback) { if (_targetPropertyExpression != null) @@ -813,53 +844,66 @@ public Binding WhenSourceChanges(Action callback) if (_onSourceUpdateWithParameter.IsAlive) { - _onSourceUpdateWithParameter.Execute(_sourcePropertyFunc.Invoke()); + _onSourceUpdateWithParameter.Execute(_sourcePropertyFunc!.Invoke()); } return this; } - public Binding WhenSourceChanges(Func func) + /// + /// Defines an generic function that will be executed every time that the binding value changes. + /// + /// + /// The function that will be executed when the binding changes. + /// IMPORTANT: Note that closures are not supported at the moment + /// due to the use of WeakActions (see http://stackoverflow.com/questions/25730530/). + /// + /// The Binding instance. + /// + /// When the method is called on a binding that already has a target property set. + /// + public Binding WhenSourceChanges(Func callback) { - _sourceUpdateFunctionWithParameter = new WeakFunc(func); + _onSourceUpdateFunctionWithParameter = new WeakFunc(callback); return WhenSourceChanges(source => { - _sourceUpdateFunctionWithParameter.Execute(_sourcePropertyFunc.Invoke()); + _onSourceUpdateFunctionWithParameter.Execute(_sourcePropertyFunc!.Invoke()); }); } + /// + /// Defines the behavior to update the binding when the source control's property changes. + /// + /// The Binding instance. protected abstract Binding CheckControlSource(); + /// + /// Defines the behavior to update the binding when the source control's property changes. + /// + /// The Binding instance. protected abstract Binding CheckControlTarget(); - private void Attach( - object source, - object target, - BindingMode mode) + private void Attach(object? source, object? target, BindingMode mode) { Attach(source, target, mode, _resolveTopField); } - private void Attach( - object source, - object target, - BindingMode mode, - bool resolveTopField) + private void Attach(object? source, object? target, BindingMode mode, bool resolveTopField) { _resolveTopField = resolveTopField; var sourceChain = GetPropertyChain( source, null, - _sourcePropertyExpression.Body as MemberExpression, - _sourcePropertyName, + _sourcePropertyExpression!.Body as MemberExpression, + _sourcePropertyName!, resolveTopField); var lastSourceInChain = sourceChain.Last(); sourceChain.Remove(lastSourceInChain); - PropertySource = new WeakReference(lastSourceInChain.Instance); + _propertySource = new WeakReference(lastSourceInChain.Instance); if (mode != BindingMode.OneTime) { @@ -888,7 +932,7 @@ private void Attach( var lastTargetInChain = targetChain.Last(); targetChain.Remove(lastTargetInChain); - PropertyTarget = new WeakReference(lastTargetInChain.Instance); + _propertyTarget = new WeakReference(lastTargetInChain.Instance); if (mode != BindingMode.OneTime) { @@ -923,34 +967,35 @@ private void Attach( private void Attach() { - if (PropertyTarget != null - && PropertyTarget.IsAlive - && PropertyTarget.Target != null + if (_propertyTarget != null + && _propertyTarget.IsAlive + && _propertyTarget.Target != null && !string.IsNullOrEmpty(_targetPropertyName)) { - var targetType = PropertyTarget.Target.GetType(); - _targetProperty = targetType.GetRuntimeProperty(_targetPropertyName); + var targetType = _propertyTarget.Target.GetType(); + _targetProperty = targetType.GetRuntimeProperty(_targetPropertyName); if (_targetProperty == null) { - throw new InvalidOperationException("Property not found: " + _targetPropertyName); + throw new InvalidOperationException($"Property not found: {_targetPropertyName}"); } } - if (PropertySource == null - || !PropertySource.IsAlive - || PropertySource.Target == null) + if (_propertySource == null + || !_propertySource.IsAlive + || _propertySource.Target == null) { SetSpecialValues(); return; } - var sourceType = PropertySource.Target.GetType(); + var sourceType = _propertySource.Target.GetType(); + _sourceProperty = sourceType.GetRuntimeProperty(_sourcePropertyName); if (_sourceProperty == null) { - throw new InvalidOperationException("Property not found: " + _sourcePropertyName); + throw new InvalidOperationException($"Property not found: {_sourcePropertyName}"); } // OneTime binding @@ -959,12 +1004,12 @@ private void Attach() var value = GetSourceValue(); if (_targetProperty != null - && PropertyTarget != null - && PropertyTarget.IsAlive - && PropertyTarget.Target != null) + && _propertyTarget != null + && _propertyTarget.IsAlive + && _propertyTarget.Target != null) { _settingSourceToTarget = true; - SetTargetValue(value); + SetTargetValue(value!); _settingSourceToTarget = false; } @@ -977,7 +1022,7 @@ private void Attach() if (_onSourceUpdateWithParameter != null && _onSourceUpdateWithParameter.IsAlive) { - _onSourceUpdateWithParameter.Execute(_sourcePropertyFunc.Invoke()); + _onSourceUpdateWithParameter.Execute(_sourcePropertyFunc!.Invoke()); } } @@ -987,7 +1032,7 @@ private void Attach() } // Check OneWay binding - if (PropertySource.Target is INotifyPropertyChanged inpc) + if (_propertySource.Target is INotifyPropertyChanged inpc) { var listener = new PropertyChangedEventListener(this, inpc, true); _listeners.Add(listener); @@ -1007,11 +1052,11 @@ private void Attach() // Check TwoWay binding if (_onSourceUpdate == null && _onSourceUpdateWithParameter == null - && PropertyTarget != null - && PropertyTarget.IsAlive - && PropertyTarget.Target != null) + && _propertyTarget != null + && _propertyTarget.IsAlive + && _propertyTarget.Target != null) { - if (PropertyTarget.Target is INotifyPropertyChanged inpc2) + if (_propertyTarget.Target is INotifyPropertyChanged inpc2) { var listener = new PropertyChangedEventListener(this, inpc2, false); _listeners.Add(listener); @@ -1024,9 +1069,9 @@ private void Attach() } } - private bool CanBeConverted(PropertyInfo sourceProperty, PropertyInfo targetProperty) + private bool CanBeConverted(PropertyInfo? sourceProperty, PropertyInfo? targetProperty) { - if (targetProperty == null) + if (sourceProperty == null || targetProperty == null) { return true; } @@ -1038,58 +1083,58 @@ private bool CanBeConverted(PropertyInfo sourceProperty, PropertyInfo targetProp || (IsValueType(sourceType) && IsValueType(targetType)); } - private void DetachSourceHandlers() + private void DetachAllSourceHandlers() { - if (PropertySource == null - || !PropertySource.IsAlive - || PropertySource.Target == null) + if (_propertySource == null + || !_propertySource.IsAlive + || _propertySource.Target == null) { return; } foreach (var eventName in SourceHandlers.Keys) { - var type = PropertySource.Target.GetType(); - var ev = type.GetRuntimeEvent(eventName); - if (ev == null) + var type = _propertySource.Target.GetType(); + var @event = type.GetRuntimeEvent(eventName); + if (@event == null) { return; } - ev.RemoveEventHandler(PropertySource.Target, SourceHandlers[eventName].Delegate); + @event.RemoveEventHandler(_propertySource.Target, SourceHandlers[eventName].Delegate); } SourceHandlers.Clear(); } - private void DetachTargetHandlers() + private void DetachAllTargetHandlers() { - if (PropertySource == null - || !PropertySource.IsAlive - || PropertySource.Target == null) + if (_propertySource == null + || !_propertySource.IsAlive + || _propertySource.Target == null) { return; } foreach (var eventName in TargetHandlers.Keys) { - var type = PropertyTarget.Target.GetType(); - var ev = type.GetRuntimeEvent(eventName); - if (ev == null) + var type = _propertyTarget!.Target.GetType(); + var @event = type.GetRuntimeEvent(eventName); + if (@event == null) { return; } - ev.RemoveEventHandler(PropertyTarget.Target, TargetHandlers[eventName].Delegate); + @event.RemoveEventHandler(_propertyTarget.Target, TargetHandlers[eventName].Delegate); } TargetHandlers.Clear(); } - private static IList GetPropertyChain( - object topInstance, - IList instances, - MemberExpression expression, + private IList GetPropertyChain( + object? topInstance, + IList? instances, + MemberExpression? expression, string propertyName, bool resolveTopField, bool top = true) @@ -1099,9 +1144,8 @@ private static IList GetPropertyChain( instances = new List(); } - var ex = expression.Expression as MemberExpression; - - if (ex == null) + var expr = expression!.Expression as MemberExpression; + if (expr == null) { if (top) { @@ -1116,7 +1160,7 @@ private static IList GetPropertyChain( return instances; } - var list = GetPropertyChain(topInstance, instances, ex, propertyName, resolveTopField, false); + var list = GetPropertyChain(topInstance, instances, expr, propertyName, resolveTopField, false); if (list.Count == 0) { @@ -1143,9 +1187,7 @@ private static IList GetPropertyChain( if (lastInstance.Instance != null) { - var prop = ex.Member as PropertyInfo; - - if (prop != null) + if (expr.Member is PropertyInfo prop) { try { @@ -1161,14 +1203,14 @@ private static IList GetPropertyChain( } catch (TargetInvocationException) { + // ignored } } else { - if (lastInstance.Instance == topInstance - && resolveTopField) + if (lastInstance.Instance == topInstance && resolveTopField) { - var field = ex.Member as FieldInfo; + var field = expr.Member as FieldInfo; if (field != null) { try @@ -1186,8 +1228,7 @@ private static IList GetPropertyChain( catch (ArgumentException) { throw new InvalidOperationException( - "Are you trying to use SetBinding with a local variable? " - + "Try to use new Binding instead"); + "Are you trying to use SetBinding with a local variable? Try to use new Binding instead"); } } } @@ -1202,7 +1243,11 @@ private static IList GetPropertyChain( return list; } - private static string GetPropertyName(Expression> propertyExpression) + [SuppressMessage( + "StyleCop.CSharp.OrderingRules", + "SA1204:Static elements should appear before instance elements", + Justification = "Revieved.")] + private static string? GetPropertyName(Expression>? propertyExpression) { if (propertyExpression == null) { @@ -1210,14 +1255,12 @@ private static string GetPropertyName(Expression> propertyExpression) } var body = propertyExpression.Body as MemberExpression; - if (body == null) { throw new ArgumentException("Invalid argument", nameof(propertyExpression)); } var property = body.Member as PropertyInfo; - if (property == null) { throw new ArgumentException("Argument is not a property", nameof(propertyExpression)); @@ -1226,6 +1269,7 @@ private static string GetPropertyName(Expression> propertyExpression) return property.Name; } + [return: MaybeNull] private TTarget GetSourceValue() { if (_sourceProperty == null) @@ -1233,7 +1277,7 @@ private TTarget GetSourceValue() return default; } - var sourceValue = (TSource) _sourceProperty.GetValue(PropertySource.Target, null); + var sourceValue = (TSource) _sourceProperty.GetValue(_propertySource!.Target, null); try { @@ -1246,14 +1290,15 @@ private TTarget GetSourceValue() return _converter.Convert(FallbackValue); } - var targetValue = (TTarget) _targetProperty.GetValue(PropertyTarget.Target, null); + var targetValue = (TTarget) _targetProperty!.GetValue(_propertyTarget!.Target, null); return targetValue; } } + [return: MaybeNull] private TSource GetTargetValue() { - var targetValue = (TTarget) _targetProperty.GetValue(PropertyTarget.Target, null); + var targetValue = (TTarget) _targetProperty!.GetValue(_propertyTarget!.Target, null); try { @@ -1261,23 +1306,23 @@ private TSource GetTargetValue() } catch (Exception) { - var sourceValue = (TSource) _sourceProperty.GetValue(PropertySource.Target, null); + var sourceValue = (TSource) _sourceProperty!.GetValue(_propertySource!.Target, null); return sourceValue; } } private void HandleSourceEvent(object sender, TEventArgs args) { - if (PropertyTarget != null - && PropertyTarget.IsAlive - && PropertyTarget.Target != null - && PropertySource != null - && PropertySource.IsAlive - && PropertySource.Target != null + if (_propertyTarget != null + && _propertyTarget.IsAlive + && _propertyTarget.Target != null + && _propertySource != null + && _propertySource.IsAlive + && _propertySource.Target != null && !_settingTargetToSource) { var valueLocal = GetSourceValue(); - var targetValue = _targetProperty.GetValue(PropertyTarget.Target, null); + var targetValue = _targetProperty!.GetValue(_propertyTarget.Target, null); if (Equals(valueLocal, targetValue)) { @@ -1287,29 +1332,29 @@ private void HandleSourceEvent(object sender, TEventArgs args) if (_targetProperty != null) { _settingSourceToTarget = true; - SetTargetValue(valueLocal); + SetTargetValue(valueLocal!); _settingSourceToTarget = false; } } _onSourceUpdate?.Execute(); - _onSourceUpdateWithParameter?.Execute(_sourcePropertyFunc.Invoke()); + _onSourceUpdateWithParameter?.Execute(_sourcePropertyFunc!.Invoke()); RaiseValueChanged(); } private void HandleTargetEvent(object source, TEventArgs args) { - if (PropertyTarget != null - && PropertyTarget.IsAlive - && PropertyTarget.Target != null - && PropertySource != null - && PropertySource.IsAlive - && PropertySource.Target != null + if (_propertyTarget != null + && _propertyTarget.IsAlive + && _propertyTarget.Target != null + && _propertySource != null + && _propertySource.IsAlive + && _propertySource.Target != null && !_settingSourceToTarget) { var valueLocal = GetTargetValue(); - var sourceValue = _sourceProperty.GetValue(PropertySource.Target, null); + var sourceValue = _sourceProperty!.GetValue(_propertySource.Target, null); if (Equals(valueLocal, sourceValue)) { @@ -1317,7 +1362,7 @@ private void HandleTargetEvent(object source, TEventArgs args) } _settingTargetToSource = true; - SetSourceValue(valueLocal); + SetSourceValue(valueLocal!); _settingTargetToSource = false; } @@ -1331,7 +1376,7 @@ private bool IsSourceDefaultValue() return true; } - var sourceValue = (TSource) _sourceProperty.GetValue(PropertySource.Target, null); + var sourceValue = (TSource) _sourceProperty.GetValue(_propertySource!.Target, null); return Equals(default(TSource), sourceValue); } @@ -1346,16 +1391,16 @@ private void RaiseValueChanged() handler?.Invoke(this, EventArgs.Empty); } - private void SetSourceValue(TSource value) + private void SetSourceValue([MaybeNull] TSource value) { - _sourceProperty.SetValue(PropertySource.Target, value, null); + _sourceProperty!.SetValue(_propertySource!.Target, value, null); } private bool SetSpecialValues() { if (_isFallbackValueActive) { - _targetProperty.SetValue(PropertyTarget.Target, _converter.Convert(FallbackValue), null); + _targetProperty!.SetValue(_propertyTarget!.Target, _converter.Convert(FallbackValue), null); return true; } @@ -1363,7 +1408,7 @@ private bool SetSpecialValues() { if (IsSourceDefaultValue()) { - _targetProperty.SetValue(PropertyTarget.Target, _converter.Convert(TargetNullValue), null); + _targetProperty!.SetValue(_propertyTarget!.Target, _converter.Convert(TargetNullValue), null); return true; } } @@ -1371,19 +1416,14 @@ private bool SetSpecialValues() return false; } - private void SetTargetValue(TTarget value) + private void SetTargetValue([MaybeNull] TTarget value) { if (!SetSpecialValues()) { - _targetProperty.SetValue(PropertyTarget.Target, value, null); + _targetProperty!.SetValue(_propertyTarget!.Target, value, null); } } - /// - /// Occurs when the value of the databound property changes. - /// - public override event EventHandler ValueChanged; - internal class ObjectSwappedEventListener : IWeakEventListener { private readonly WeakReference _bindingReference; @@ -1400,11 +1440,8 @@ public ObjectSwappedEventListener( public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) { - var propArgs = e as PropertyChangedEventArgs; - if (InstanceReference.Target == sender - && propArgs != null - && _bindingReference != null + && e is PropertyChangedEventArgs && _bindingReference.IsAlive && _bindingReference.Target != null) { @@ -1412,10 +1449,7 @@ public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) binding.Detach(); - binding.Attach( - binding.Source, - binding.Target, - binding.Mode); + binding.Attach(binding.Source, binding.Target, binding.Mode); return true; } @@ -1426,8 +1460,8 @@ public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) internal class PropertyAndName { - public object Instance; - public string Name; + public object? Instance; + public string? Name; } internal class PropertyChangedEventListener : IWeakEventListener @@ -1452,17 +1486,16 @@ public PropertyChangedEventListener( public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) { - if (_bindingReference != null - && _bindingReference.IsAlive + if (_bindingReference.IsAlive && _bindingReference.Target != null) { var binding = (Binding) _bindingReference.Target; if (_updateFromSourceToTarget) { - if (binding.PropertySource != null - && binding.PropertySource.IsAlive - && sender == binding.PropertySource.Target) + if (binding._propertySource != null + && binding._propertySource.IsAlive + && sender == binding._propertySource.Target) { if (!binding._settingTargetToSource) { @@ -1474,9 +1507,9 @@ public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) } else { - if (binding.PropertyTarget != null - && binding.PropertyTarget.IsAlive - && sender == binding.PropertyTarget.Target) + if (binding._propertyTarget != null + && binding._propertyTarget.IsAlive + && sender == binding._propertyTarget.Target) { if (!binding._settingSourceToTarget) { @@ -1494,21 +1527,32 @@ public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) } } + /// + /// Extends delegate with additional properties. + /// + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Reviewed.")] public class DelegateInfo { - public Delegate Delegate; + /// + /// The delegate instance. + /// + public Delegate? Delegate; + + /// + /// Indicates that this delegate should be used as default. + /// public bool IsDefault; } - private class SimpleConverter + private class WeakConverter { - private WeakFunc _convert; - private WeakFunc _convertBack; + private WeakFunc? _convert; + private WeakFunc? _convertBack; + [return: MaybeNull] public TTarget Convert(TSource value) { - if (_convert != null - && _convert.IsAlive) + if (_convert is { IsAlive: true }) { return _convert.Execute(value); } @@ -1523,10 +1567,10 @@ public TTarget Convert(TSource value) } } + [return: MaybeNull] public TSource ConvertBack(TTarget value) { - if (_convertBack != null - && _convertBack.IsAlive) + if (_convertBack is { IsAlive: true }) { return _convertBack.Execute(value); } @@ -1551,12 +1595,13 @@ public void SetConvertBack(Func convertBack) _convertBack = new WeakFunc(convertBack); } - private TTo ConvertSafely(TFrom value) + [return: MaybeNull] + private static TTo ConvertSafely(TFrom value) { try { var notNullableFromType = Nullable.GetUnderlyingType(typeof(TFrom)); - object notNullableValue = value; + object? notNullableValue = value; if (notNullableFromType != null) { if (value == null) diff --git a/Softeq.XToolkit.Bindings/Extensions/BindableExtensions.cs b/Softeq.XToolkit.Bindings/Extensions/BindableExtensions.cs index d79d1cf51..f1dc674ab 100644 --- a/Softeq.XToolkit.Bindings/Extensions/BindableExtensions.cs +++ b/Softeq.XToolkit.Bindings/Extensions/BindableExtensions.cs @@ -8,6 +8,9 @@ namespace Softeq.XToolkit.Bindings.Extensions { + /// + /// Extensions for the Bindable components. + /// public static class BindableExtensions { /// @@ -16,9 +19,9 @@ public static class BindableExtensions /// to the source property. If the target implements INotifyPropertyChanged, has observable properties and /// the BindingMode is TwoWay, the source will also be notified of changes to the target's properties. /// - /// The type of the source property that is being databound. + /// The type of the source property that is being data-bound. /// - /// The type of the target property that is being databound. If the source type + /// The type of the target property that is being data-bound. If the source type /// is not the same as the target type, an automatic conversion will be attempted. However only /// simple types can be converted. For more complex conversions, use the /// @@ -52,7 +55,7 @@ public static Binding Bind( Expression> targetPropertyExpression, BindingMode mode = BindingMode.Default) { - var binding = source.SetBinding(sourcePropertyExpression, targetPropertyExpression, mode); + var binding = source.SetBinding(sourcePropertyExpression!, targetPropertyExpression, mode); SetBindingTo(source, binding); @@ -68,9 +71,9 @@ public static Binding Bind( /// The method provides the ability for advanced configuration of internal /// object. /// - /// The type of the source property that is being databound. + /// The type of the source property that is being data-bound. /// - /// The type of the target property that is being databound. If the source type + /// The type of the target property that is being data-bound. If the source type /// is not the same as the target type, an automatic conversion will be attempted. However only /// simple types can be converted. For more complex conversions, use the /// @@ -110,9 +113,9 @@ public static Binding Bind( BindingMode mode, Func, Binding> configure) { - var binding = source.SetBinding(sourcePropertyExpression, targetPropertyExpression, mode); + var binding = source.SetBinding(sourcePropertyExpression!, targetPropertyExpression, mode); - var resultBinding = configure(binding); + var resultBinding = configure(binding!); SetBindingTo(source, resultBinding); @@ -125,9 +128,9 @@ public static Binding Bind( /// to the source property. If the target implements INotifyPropertyChanged, has observable properties and /// the BindingMode is TwoWay, the source will also be notified of changes to the target's properties. /// - /// The type of the source property that is being databound. + /// The type of the source property that is being data-bound. /// - /// The type of the target property that is being databound. If the source type + /// The type of the target property that is being data-bound. If the source type /// is not the same as the target type, an automatic conversion will be attempted. However only /// simple types can be converted. For more complex conversions, use the /// @@ -158,8 +161,8 @@ public static Binding Bind( IConverter converter) { var binding = source - .SetBinding(sourcePropertyExpression, targetPropertyExpression) - .SetConverter(converter); + .SetBinding(sourcePropertyExpression!, targetPropertyExpression) + .SetConverter(converter!); SetBindingTo(source, binding); @@ -172,9 +175,9 @@ public static Binding Bind( /// to the source property. If the target implements INotifyPropertyChanged, has observable properties and /// the BindingMode is TwoWay, the source will also be notified of changes to the target's properties. /// - /// The type of the source property that is being databound. + /// The type of the source property that is being data-bound. /// - /// The type of the target property that is being databound. If the source type + /// The type of the target property that is being data-bound. If the source type /// is not the same as the target type, an automatic conversion will be attempted. However only /// simple types can be converted. For more complex conversions, use the /// @@ -213,8 +216,8 @@ public static Binding Bind( IConverter converter) { var binding = source - .SetBinding(sourcePropertyExpression, targetPropertyExpression, mode) - .SetConverter(converter); + .SetBinding(sourcePropertyExpression!, targetPropertyExpression, mode) + .SetConverter(converter!); SetBindingTo(source, binding); @@ -250,7 +253,9 @@ public static Binding Bind( Expression> sourcePropertyExpression, Action action) { - var binding = source.SetBinding(sourcePropertyExpression).WhenSourceChanges(action); + var binding = source + .SetBinding(sourcePropertyExpression!) + .WhenSourceChanges(action!); SetBindingTo(source, binding); @@ -294,7 +299,9 @@ public static Binding Bind( Action action, BindingMode mode) { - var binding = source.SetBinding(sourcePropertyExpression, mode).WhenSourceChanges(action); + var binding = source + .SetBinding(sourcePropertyExpression!, mode) + .WhenSourceChanges(action!); SetBindingTo(source, binding); @@ -304,9 +311,7 @@ public static Binding Bind( /// /// Detach all bindings of the source. /// - /// - /// The source of the bindings. - /// + /// The source of the bindings. public static void DetachBindings(this IBindingsOwner source) { source.Bindings?.DetachAllAndClear(); diff --git a/Softeq.XToolkit.Bindings/IBindingFactory.cs b/Softeq.XToolkit.Bindings/IBindingFactory.cs index ef92e4c7c..1f23d10ac 100644 --- a/Softeq.XToolkit.Bindings/IBindingFactory.cs +++ b/Softeq.XToolkit.Bindings/IBindingFactory.cs @@ -2,12 +2,16 @@ // http://www.softeq.com using System; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; using System.Windows.Input; namespace Softeq.XToolkit.Bindings { + /// + /// Defines methods for creating bindings. + /// public interface IBindingFactory { /// @@ -33,9 +37,9 @@ public interface IBindingFactory /// /// The value to use when the binding is unable to return a value. /// - /// The type of the source property that is being databound. + /// The type of the source property that is being data bound. /// - /// The type of the target property that is being databound. + /// The type of the target property that is being data bound. /// If the source type is not the same as the target type, an automatic conversion will be attempted. /// However only simple types can be converted. For more complex conversions, /// use the @@ -49,8 +53,8 @@ Binding CreateBinding( object? target = null, Expression>? targetPropertyExpression = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default!, - TSource targetNullValue = default!); + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!); /// /// Creates a new instance of the class @@ -74,9 +78,9 @@ Binding CreateBinding( /// /// The value to use when the binding is unable to return a value. /// - /// The type of the source property that is being databound. + /// The type of the source property that is being data bound. /// - /// The type of the target property that is being databound. + /// The type of the target property that is being data bound. /// If the source type is not the same as the target type, an automatic conversion will be attempted. /// However only simple types can be converted. For more complex conversions, /// use the @@ -89,8 +93,8 @@ Binding CreateBinding( object? target = null, Expression>? targetPropertyExpression = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default!, - TSource targetNullValue = default!); + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!); /// /// Creates a new instance of the class @@ -114,9 +118,9 @@ Binding CreateBinding( /// /// The value to use when the binding is unable to return a value. /// - /// The type of the source property that is being databound. + /// The type of the source property that is being data bound. /// - /// The type of the target property that is being databound. + /// The type of the target property that is being data bound. /// If the source type is not the same as the target type, an automatic conversion will be attempted. /// However only simple types can be converted. For more complex conversions, /// use the @@ -129,8 +133,8 @@ Binding CreateBinding( object? target = null, string? targetPropertyName = null, BindingMode mode = BindingMode.Default, - TSource fallbackValue = default!, - TSource targetNullValue = default!); + [MaybeNull] TSource fallbackValue = default!, + [MaybeNull] TSource targetNullValue = default!); /// /// Creates a new instance of the that will wrap the command. @@ -167,7 +171,7 @@ Delegate GetCommandHandler( string eventName, Type elementType, ICommand command, - Binding castedBinding); + Binding? castedBinding); /// /// Creates a new instance of the that will wrap the command. @@ -225,9 +229,14 @@ Delegate GetCommandHandler( string eventName, Type elementType, ICommand command, - Binding castedBinding); + Binding? castedBinding); - string GetDefaultEventNameForControl(Type type); + /// + /// Gets the name of the default event to use when binding, based on the of control. + /// + /// The type of control. + /// The name of the control event. + string? GetDefaultEventNameForControl(Type type); /// /// Provides the way for command to give a feedback to the element when CanExecuteChanged event is triggered. @@ -239,6 +248,6 @@ Delegate GetCommandHandler( /// that will passed to the . Used to determine new value of CanExecute. /// /// Type of command parameter. - void HandleCommandCanExecute(object element, ICommand command, Binding commandParameterBinding); + void HandleCommandCanExecute(object element, ICommand command, Binding? commandParameterBinding); } } diff --git a/Softeq.XToolkit.Bindings/UpdateSourceTriggerMode.cs b/Softeq.XToolkit.Bindings/UpdateTriggerMode.cs similarity index 84% rename from Softeq.XToolkit.Bindings/UpdateSourceTriggerMode.cs rename to Softeq.XToolkit.Bindings/UpdateTriggerMode.cs index 2d7e138fd..ff568bcb9 100644 --- a/Softeq.XToolkit.Bindings/UpdateSourceTriggerMode.cs +++ b/Softeq.XToolkit.Bindings/UpdateTriggerMode.cs @@ -9,15 +9,13 @@ namespace Softeq.XToolkit.Bindings public enum UpdateTriggerMode { /// - /// Defines that the binding should be updated when the control - /// loses the focus. + /// Defines that the binding should be updated when the control loses the focus. /// LostFocus, /// - /// Defines that the binding should be updated when the control's - /// bound property changes. + /// Defines that the binding should be updated when the control's bound property changes. /// PropertyChanged } -} \ No newline at end of file +}