- Hands-On Mobile Development with .NET Core
- Can Bilgin
- 652字
- 2021-06-24 13:55:40
Creating behaviors
Behaviors are an eloquent use of the decorator pattern, allowing the developers to modify the Xamarin.Forms controls without having to create derived controls.
A simple example of creating a behavior would be to implement a validation behavior in our LoginView. As you may remember, we actually used the Command.CanExecute delegate to validate our fields. In this example, we will separate the validators for the email field and the password field. This way, we can allow the UI to give feedback to the user as a result of an incorrect entry. This would be more user-friendly than only disabling the login window. To set this up, follow these steps:
- First, we need to create a validation rule infrastructure, starting with the validation interface:
public interface IValidationRule<T>
{
string ValidationMessage { get; set; }
bool Validate (T value);
}
- A simple implementation of this rule would be required so that we can check whether we have a short validation message stating that the field is a required field:
public class RequiredValidationRule : IValidationRule<string>
{
public string ValidationMessage { get; set; } = "This field is
a required field";
public bool Validate (string value)
{
return !string.IsNullOrEmpty(value);
}
}
- Now, we can create our validation behavior for the Entry field, which will make use of any given validation rule (starting with RequiredValidationRule, which we just implemented):
public class ValidationBehavior : Behavior<Entry>
{
protected override void OnAttachedTo(Entry bindable)
{
base.OnAttachedTo(bindable);
bindable.TextChanged += ValidateField;
}
protected override void OnDetachingFrom(Entry bindable)
{
base.OnDetachingFrom(bindable);
bindable.TextChanged -= ValidateField;
}
private void ValidateField(object sender, TextChangedEventArgs
args)
{
if (sender is Entry entry)
{
// TODO:
}
}
}
In this implementation, the OnAttachedTo and OnDetachingFrom methods are the crucial access points and the teardown logic implementations. In this case, when the behavior is attached to a target control, we are subscribing to the TextChanged event, and when the behavior is removed, we are unsubscribing from the event so that undesired memory leak issues are avoided.
- The next order of business will be to implement a bindable property for the validation rule so that the validation rules are dictated by the view model (or another business logic module), decoupling it from the view:
public static readonly BindableProperty ValidationRuleProperty =
BindableProperty.CreateAttached("ValidationRule", typeof(IValidationRule<string>), typeof(ValidationBehavior), null);
public static readonly BindableProperty HasErrorProperty =
BindableProperty.CreateAttached("HasError", typeof(bool), typeof(ValidationBehavior), false, BindingMode.TwoWay);
public IValidationRule<string> ValidationRule
{
get { return this.GetValue(ValidationRuleProperty) as IValidationRule<string>; }
set { this.SetValue(ValidationRuleProperty, value); }
}
public bool HasError
{
get { return (bool) GetValue(HasErrorProperty); }
set { SetValue(HasErrorProperty, value); }
}
- Now that we have an outlet for the validation rule and an output field (so that we can attach additional UX logic to it), we can implement the validate method:
private void ValidateField(object sender, TextChangedEventArgs
args)
{
if (sender is Entry entry && ValidationRule != null)
{
if (!ValidationRule.Validate(args.NewTextValue))
{
entry.BackgroundColor = Color.Crimson;
HasError = true;
}
else
{
entry.BackgroundColor = Color.White;
HasError = false;
}
}
}
- After adding the appropriate rule to the view model property (in this case, UserNameValidation), we can bind the behavior to the validation rule that's exposed from the view model and observe the entry field behavior according to the text input:
<Entry x:Name="usernameEntry" Placeholder="username" Text="{Binding UserName, Mode=OneWayToSource}" >
<Entry.Behaviors>
<behaviors:ValidationBehavior x:Name="UserNameValidation"
ValidationRule="{Binding
BindingContext.UserNameValidation,
Source={x:Reference RootView}}" />
</Entry.Behaviors>
</Entry>
Here, the main benefit is that we do not have to modify the Entry field, and the implemented behavior can be maintained as a separate module.
- To extend this implementation, we can add a validation error message label that will display in accordance with the HasError bindable property (anywhere on the page layout, as long as the UserNameValidation element is accessible):
<Label Text="UserName is required" FontSize="12" TextColor="Gray"
IsVisible="{Binding HasError, Source={x:Reference UserNameValidation}}"/>
- The outcome would look similar to the following: