Luis de Haro
Luis de Haro

Reputation: 739

Xamarin.Forms Entry - Custom Behaviors and MVVM

I would like to add some validations for my Xamarin Forms project. These are some very basic ones like:

I'm using MVVM Light in my project and because of that, I'm not using code behind in my Pages.

I'm using the below code, trying to bind the value of the Behavior to a property in my ViewModel.

EmailValidatorBehavior.cs:

public class EmailValidatorBehavior : Behavior<Entry>
    {
        const string emailRegex = @"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" +
            @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$";

        public static readonly BindablePropertyKey IsValidPropertyKey = BindableProperty.CreateReadOnly("IsValid", typeof(bool), typeof(EmailValidatorBehavior), false);

        public static readonly BindableProperty IsValidProperty = IsValidPropertyKey.BindableProperty;

        public bool IsValid
        {
            get { return (bool)base.GetValue(IsValidProperty); }
            private set { base.SetValue(IsValidPropertyKey, value); }
        }

        protected override void OnAttachedTo(Entry bindable)
        {
            bindable.TextChanged += HandleTextChanged;
        }

        void HandleTextChanged(object sender, TextChangedEventArgs e)
        {
            IsValid = (Regex.IsMatch(e.NewTextValue, emailRegex, RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250)));
            ((Entry)sender).TextColor = IsValid ? Color.Default : Color.Red;
        }

        protected override void OnDetachingFrom(Entry bindable)
        {
            bindable.TextChanged -= HandleTextChanged;

        }
    }

View.xaml:

<Entry 
    Placeholder="E-mail"
    Text="{Binding Path=User.email, Mode=TwoWay}"
    Keyboard="Email">

    <Entry.Behaviors>
        <EmailValidatorBehavior x:Name="emailValidator" IsValid="{Binding Path=IsEmailValid, Mode=TwoWay}" />
    </Entry.Behaviors>
</Entry>

ViewModel.cs:

private bool _IsEmailValid = false;
public bool IsEmailValid
{
    get
    {
        return _IsEmailValid;
    }
    set
    {
        _IsEmailValid = value;
        RaisePropertyChanged("IsEmailValid");
    }
}

The value of the IsEmailValid never changes, even though the email is correct and the IsValid property of the behavior turns to true. What could be wrong?

Thanks in advance.

Upvotes: 3

Views: 4738

Answers (2)

Joshua Poling
Joshua Poling

Reputation: 571

It looks like you have set everything up correctly besides your xaml local behavior attachment. You need to change the following:

Original Example

<EmailValidatorBehavior x:Name="emailValidator" IsValid="{Binding Path=IsEmailValid, Mode=TwoWay}" />

Updated Code

  <Entry Placeholder="testing">
      <Entry.Behaviors>
          <local:EmailValidatorBehavior></local:EmailValidatorBehavior>
      </Entry.Behaviors>
  </Entry>

Output
I used your custom behavior for this example and everything is working correctly. The left side shows that IsValid = false and on the right IsValid = true.

Example output

Let me know if you have any more trouble with this. Cheers!

Upvotes: 4

Stefan
Stefan

Reputation: 17658

I was facing this issue as well and I couldn't find a satisfing answer but it seems that the BindingContext in the behaviour is not set to the page's BindingContext.

Since I am no expert on this subject, I think there will be more elegant ways, but nevertheless; this seems to work:

First, name your page to use as reference later on:

<ContentPage x:Name="Root" etc, etc>

Then, in your behavior: set the path and source to the page's binding-context:

<Entry Text="{Binding RegistrationEmail}">
    <Entry.Behaviors>
         <connector:EmailValidatorBehavior 
            IsValid="{Binding Source={x:Reference Root}, 
                      Path=BindingContext.IsRegistrationEmailValid, Mode=OneWayToSource}"/>
    </Entry.Behaviors>        
</Entry>

By the way, I think it is more appropriate to use Mode=OneWayToSource since your bind-able property is read-only.

When setting a breakpoint in your viewmodel, you will see that the boolean is updated.

Upvotes: 2

Related Questions