Skip to content

Commit

Permalink
Enable nullability for Bindings primitives (#473)
Browse files Browse the repository at this point in the history
  • Loading branch information
wcoder authored Feb 23, 2022
1 parent 1178cbd commit 26baec3
Show file tree
Hide file tree
Showing 11 changed files with 693 additions and 560 deletions.
115 changes: 56 additions & 59 deletions Softeq.XToolkit.Bindings.Droid/DroidBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
using Android.Views;
using Android.Widget;

#nullable disable

namespace Softeq.XToolkit.Bindings.Droid
{
/// <inheritdoc />
Expand All @@ -19,11 +17,11 @@ public class DroidBinding<TSource, TTarget> : Binding<TSource, TTarget>
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)
{
}
Expand All @@ -32,11 +30,11 @@ public DroidBinding(
public DroidBinding(
object source,
Expression<Func<TSource>> sourcePropertyExpression,
object target = null,
Expression<Func<TTarget>> targetPropertyExpression = null,
object? target = null,
Expression<Func<TTarget>>? 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)
{
}
Expand All @@ -46,11 +44,11 @@ public DroidBinding(
object source,
Expression<Func<TSource>> sourcePropertyExpression,
bool? resolveTopField,
object target = null,
Expression<Func<TTarget>> targetPropertyExpression = null,
object? target = null,
Expression<Func<TTarget>>? targetPropertyExpression = null,
BindingMode mode = BindingMode.Default,
TSource fallbackValue = default,
TSource targetNullValue = default)
[MaybeNull] TSource fallbackValue = default!,
[MaybeNull] TSource targetNullValue = default!)
: base(
source,
sourcePropertyExpression,
Expand All @@ -64,20 +62,23 @@ public DroidBinding(
}

/// <summary>
/// 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.
/// </summary>
/// <param name="mode">
/// Defines the binding's update mode. Use
/// <see cref="UpdateTriggerMode.LostFocus" /> to update the binding when
/// the source control loses the focus. You can also use
/// <see cref="UpdateTriggerMode.PropertyChanged" /> to update the binding
/// Defines the binding's update mode.
///
/// Use <see cref="UpdateTriggerMode.LostFocus" /> to update the binding when the source control loses the focus.
/// You can also use <see cref="UpdateTriggerMode.PropertyChanged" /> to update the binding
/// when the source control's property changes.
///
/// The PropertyChanged mode should only be used with the following items:
/// <para>- an EditText control and its Text property (TextChanged event).</para>
/// <para>- a CompoundButton control and its Checked property (CheckedChange event).</para>
/// <para>- <see cref="T:Android.Widget.EditText"/> control and its <c>Text</c> property (<c>TextChanged</c> event).</para>
/// <para>- <see cref="T:Android.Widget.CompoundButton"/> control and its <c>Checked</c> property (<c>CheckedChange</c> event).</para>
/// </param>
/// <returns>The Binding instance.</returns>
/// <exception cref="T:System.InvalidOperationException">
Expand All @@ -87,60 +88,55 @@ public DroidBinding(
/// </exception>
public Binding<TSource, TTarget> ObserveSourceEvent(UpdateTriggerMode mode = UpdateTriggerMode.PropertyChanged)
{
switch (mode)
return mode switch
{
case UpdateTriggerMode.LostFocus:
return ObserveSourceEvent<View.FocusChangeEventArgs>("FocusChange");

case UpdateTriggerMode.PropertyChanged:
return CheckControlSource();
}

return this;
UpdateTriggerMode.LostFocus => ObserveSourceEvent<View.FocusChangeEventArgs>("FocusChange"),
UpdateTriggerMode.PropertyChanged => CheckControlSource(),
_ => this
};
}

/// <summary>
/// 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.
/// </summary>
/// <param name="mode">
/// Defines the binding's update mode. Use
/// <see cref="UpdateTriggerMode.LostFocus" /> to update the binding when
/// the source control loses the focus. You can also use
/// <see cref="UpdateTriggerMode.PropertyChanged" /> to update the binding
/// when the source control's property changes.
/// Defines the binding's update mode.
///
/// Use <see cref="UpdateTriggerMode.LostFocus" /> to update the binding when the target control loses the focus.
/// You can also use <see cref="UpdateTriggerMode.PropertyChanged" /> to update the binding
/// when the target control's property changes.
///
/// The PropertyChanged mode should only be used with the following items:
/// <para>- an EditText control and its Text property (TextChanged event).</para>
/// <para>- a CompoundButton control and its Checked property (CheckedChange event).</para>
/// <para>- <see cref="T:Android.Widget.EditText"/> control and its <c>Text</c> property (<c>TextChanged</c> event).</para>
/// <para>- <see cref="T:Android.Widget.CompoundButton"/> control and its <c>Checked</c> property (<c>CheckedChange</c> event).</para>
/// </param>
/// <returns>The Binding instance.</returns>
/// <exception cref="T:System.InvalidOperationException">
/// 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.
/// </exception>
public Binding<TSource, TTarget> ObserveTargetEvent(UpdateTriggerMode mode = UpdateTriggerMode.PropertyChanged)
{
switch (mode)
return mode switch
{
case UpdateTriggerMode.LostFocus:
return ObserveTargetEvent<View.FocusChangeEventArgs>("FocusChange");

case UpdateTriggerMode.PropertyChanged:
return CheckControlTarget();
}

return this;
UpdateTriggerMode.LostFocus => ObserveTargetEvent<View.FocusChangeEventArgs>("FocusChange"),
UpdateTriggerMode.PropertyChanged => CheckControlTarget(),
_ => this
};
}

/// <inheritdoc />
[SuppressMessage("ReSharper", "RedundantAssignment", Justification = "Reviewed.")]
[SuppressMessage("ReSharper", "EntityNameCapturedOnly.Local", Justification = "Reviewed.")]
protected override Binding<TSource, TTarget> CheckControlSource()
{
switch (PropertySource.Target)
switch (_propertySource!.Target)
{
case EditText textBox:
{
Expand All @@ -161,6 +157,7 @@ protected override Binding<TSource, TTarget> CheckControlSource()
}
}

/// <inheritdoc />
[SuppressMessage("ReSharper", "RedundantAssignment", Justification = "Reviewed.")]
[SuppressMessage("ReSharper", "EntityNameCapturedOnly.Local", Justification = "Reviewed.")]
protected override Binding<TSource, TTarget> CheckControlTarget()
Expand All @@ -170,7 +167,7 @@ protected override Binding<TSource, TTarget> CheckControlTarget()
return this;
}

switch (PropertyTarget.Target)
switch (_propertyTarget!.Target)
{
case EditText textBox:
{
Expand Down
46 changes: 26 additions & 20 deletions Softeq.XToolkit.Bindings.Droid/DroidBindingFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,31 @@
// http://www.softeq.com

using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
using System.Windows.Input;
using Android.Views;
using Android.Widget;
using Softeq.XToolkit.Common.Threading;

#nullable disable

namespace Softeq.XToolkit.Bindings.Droid
{
/// <summary>
/// The Android-specific factory to create bindings.
/// </summary>
public class DroidBindingFactory : BindingFactoryBase
{
/// <inheritdoc />
public override Binding<TSource, TTarget> CreateBinding<TSource, TTarget>(
object source,
Expression<Func<TSource>> sourcePropertyExpression,
bool? resolveTopField,
object target = null,
Expression<Func<TTarget>> targetPropertyExpression = null,
object? target = null,
Expression<Func<TTarget>>? targetPropertyExpression = null,
BindingMode mode = BindingMode.Default,
TSource fallbackValue = default,
TSource targetNullValue = default)
[MaybeNull] TSource fallbackValue = default!,
[MaybeNull] TSource targetNullValue = default!)
{
return new DroidBinding<TSource, TTarget>(
source,
Expand All @@ -41,11 +43,11 @@ public override Binding<TSource, TTarget> CreateBinding<TSource, TTarget>(
public override Binding<TSource, TTarget> CreateBinding<TSource, TTarget>(
object source,
Expression<Func<TSource>> sourcePropertyExpression,
object target = null,
Expression<Func<TTarget>> targetPropertyExpression = null,
object? target = null,
Expression<Func<TTarget>>? targetPropertyExpression = null,
BindingMode mode = BindingMode.Default,
TSource fallbackValue = default,
TSource targetNullValue = default)
[MaybeNull] TSource fallbackValue = default!,
[MaybeNull] TSource targetNullValue = default!)
{
return new DroidBinding<TSource, TTarget>(
source,
Expand All @@ -61,11 +63,11 @@ public override Binding<TSource, TTarget> CreateBinding<TSource, TTarget>(
public override Binding<TSource, TTarget> CreateBinding<TSource, TTarget>(
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<TSource, TTarget>(
source,
Expand All @@ -77,12 +79,13 @@ public override Binding<TSource, TTarget> CreateBinding<TSource, TTarget>(
targetNullValue);
}

/// <inheritdoc />
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))
{
Expand All @@ -98,18 +101,19 @@ public override Delegate GetCommandHandler(
return base.GetCommandHandler(info, eventName, elementType, command, commandParameter);
}

/// <inheritdoc />
public override Delegate GetCommandHandler<T>(
EventInfo info,
string eventName,
Type elementType,
ICommand command,
Binding<T, T> castedBinding)
Binding<T, T>? castedBinding)
{
if (string.IsNullOrEmpty(eventName) && elementType == typeof(CheckBox))
{
return new EventHandler<CompoundButton.CheckedChangeEventArgs>((s, args) =>
{
object param = castedBinding == null ? default : castedBinding.Value;
var param = castedBinding == null ? default : castedBinding.Value;

if (command.CanExecute(param))
{
Expand All @@ -121,7 +125,8 @@ public override Delegate GetCommandHandler<T>(
return base.GetCommandHandler(info, eventName, elementType, command, castedBinding);
}

public override string GetDefaultEventNameForControl(Type type)
/// <inheritdoc />
public override string? GetDefaultEventNameForControl(Type type)
{
if (type == typeof(CheckBox) || typeof(CheckBox).IsAssignableFrom(type))
{
Expand All @@ -141,10 +146,11 @@ public override string GetDefaultEventNameForControl(Type type)
return null;
}

/// <inheritdoc />
public override void HandleCommandCanExecute<T>(
object element,
ICommand command,
Binding<T, T> commandParameterBinding)
Binding<T, T>? commandParameterBinding)
{
if (element is View view)
{
Expand All @@ -155,7 +161,7 @@ public override void HandleCommandCanExecute<T>(
private static void HandleViewEnabled<T>(
View view,
ICommand command,
Binding<T, T> commandParameterBinding)
Binding<T, T>? commandParameterBinding)
{
var commandParameter = commandParameterBinding == null
? default
Expand Down
Loading

0 comments on commit 26baec3

Please sign in to comment.