Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable nullability for Bindings primitives #473

Merged
merged 1 commit into from
Feb 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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