How to fire TextChanged event on a Xamarin Forms entry field programmatically?

1.5k Views Asked by At

We have setup some Xamarin behavior for not null entry fields etc, this fires when the user makes a change to a field and we then changed the entry border color, red for invalid.

However, we'd also like to reuse this behaviors when a submit button is tapped.

So I need to fire the TextChanged event manually, any ideas how I can do this, now sure if it's possible ?

public class NotEmptyEntryBehaviour : Behavior<Entry>
{
    protected override void OnAttachedTo(Entry bindable)
    {
        bindable.TextChanged += OnEntryTextChanged;
        base.OnAttachedTo(bindable);
    }

    protected override void OnDetachingFrom(Entry bindable)
    {
        bindable.TextChanged -= OnEntryTextChanged;
        base.OnDetachingFrom(bindable);
    }

    void OnEntryTextChanged(object sender, TextChangedEventArgs args)
    {
        if (args == null)
            return;

        var oldString = args.OldTextValue;
        var newString = args.NewTextValue;
    }
}
2

There are 2 best solutions below

0
Cfun On

If you want an alternative you can use one of the pre-built validation behaviors that comes with Xamarin.CommunityToolkit package, like TextValidationBehavior (by specifying a Regexp) or any more specific derived ones (example NumericValidationBehavior) that may fit your needs or even create a custom one by sub-classing ValidationBehavior.

It let you define custom styles for Valid and InValid states, but more important for the question has an async method called ForceValidate().

Also the Flags property could be interesting.

NotEmptyEntryBehaviour seems closer to TextValidationBehavior with MinimumLenght=1

xaml

 <Entry Placeholder="Type something..." x:Name="entry">
        <Entry.Behaviors>
            <xct:TextValidationBehavior Flags="ValidateOnValueChanging"
                                        InvalidStyle="{StaticResource InvalidEntryStyle}"
                                        ValidStyle="{StaticResource ValidEntryStyle}"/>
        </Entry.Behaviors>
    </Entry>

Code

await (entry.Behaviors[0] as TextValidationBehavior)?.ForceValidate();

Docs

https://learn.microsoft.com/en-us/xamarin/community-toolkit/behaviors/charactersvalidationbehavior

Repo Samples

https://github.com/xamarin/XamarinCommunityToolkit/tree/main/samples/XCT.Sample/Pages/Behaviors

EDIT

If you want to run the validation from the ViewModel you need to bind ForceValidateCommand as explained in this GitHub discussion/question.

3
Cherry Bu - MSFT On

We have setup some Xamarin behavior for not null entry fields etc, this fires when the user makes a change to a field and we then changed the entry border color, red for invalid.

You can create custom Entry with behavior to get.

The first I’m going to do is to create a new control that inherits from Entry and will add three properties: IsBorderErrorVisible, BorderErrorColor, ErrorText.

public class ExtendedEntry : Entry
{
    public static readonly BindableProperty IsBorderErrorVisibleProperty =
        BindableProperty.Create(nameof(IsBorderErrorVisible), typeof(bool), typeof(ExtendedEntry), false, BindingMode.TwoWay);

    public bool IsBorderErrorVisible
    {
        get { return (bool)GetValue(IsBorderErrorVisibleProperty); }
        set
        {
            SetValue(IsBorderErrorVisibleProperty, value);
        }
    }

    public static readonly BindableProperty BorderErrorColorProperty =
        BindableProperty.Create(nameof(BorderErrorColor), typeof(Xamarin.Forms.Color), typeof(ExtendedEntry), Xamarin.Forms.Color.Transparent, BindingMode.TwoWay);

    public Xamarin.Forms.Color BorderErrorColor
    {
        get { return (Xamarin.Forms.Color)GetValue(BorderErrorColorProperty); }
        set
        {
            SetValue(BorderErrorColorProperty, value);
        }
    }

    public static readonly BindableProperty ErrorTextProperty =
    BindableProperty.Create(nameof(ErrorText), typeof(string), typeof(ExtendedEntry), string.Empty);

    public string ErrorText
    {
        get { return (string)GetValue(ErrorTextProperty); }
        set
        {
            SetValue(ErrorTextProperty, value);
        }
    }
}

Then creating custom render to android platform.

[assembly: ExportRenderer(typeof(ExtendedEntry), typeof(ExtendedEntryRenderer))]
namespace FormsSample.Droid
{
public class ExtendedEntryRenderer : EntryRenderer
{
    public ExtendedEntryRenderer(Context context) : base(context)
    {
    }
    protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
    {
        base.OnElementChanged(e);

        if (Control == null || e.NewElement == null) return;

        UpdateBorders();
    }

    protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);

        if (Control == null) return;

        if (e.PropertyName == ExtendedEntry.IsBorderErrorVisibleProperty.PropertyName)
            UpdateBorders();
    }

    void UpdateBorders()
    {
        GradientDrawable shape = new GradientDrawable();
        shape.SetShape(ShapeType.Rectangle);
        shape.SetCornerRadius(0);

        if (((ExtendedEntry)this.Element).IsBorderErrorVisible)
        {
            shape.SetStroke(3, ((ExtendedEntry)this.Element).BorderErrorColor.ToAndroid());
        }
        else
        {
            shape.SetStroke(3, Android.Graphics.Color.LightGray);
            this.Control.SetBackground(shape);
        }

        this.Control.SetBackground(shape);
    }

}

}

Finally, Creating an Entry Behavior, handle the error to provide ui feedback to the user when validation occurs

 public class EmptyEntryValidatorBehavior : Behavior<ExtendedEntry>
{
    ExtendedEntry control;
    string _placeHolder;
    Xamarin.Forms.Color _placeHolderColor;

    protected override void OnAttachedTo(ExtendedEntry bindable)
    {
        bindable.TextChanged += HandleTextChanged;
        bindable.PropertyChanged += OnPropertyChanged;
        control = bindable;
        _placeHolder = bindable.Placeholder;
        _placeHolderColor = bindable.PlaceholderColor;
    }

    void HandleTextChanged(object sender, TextChangedEventArgs e)
    {
        ExtendedEntry customentry = (ExtendedEntry)sender;
        if (!string.IsNullOrEmpty(customentry.Text))
        {
            ((ExtendedEntry)sender).IsBorderErrorVisible = false;
        }
        else
        {
            ((ExtendedEntry)sender).IsBorderErrorVisible = true;
        }
      
    }

    protected override void OnDetachingFrom(ExtendedEntry bindable)
    {
        bindable.TextChanged -= HandleTextChanged;
        bindable.PropertyChanged -= OnPropertyChanged;
    }

    void OnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (e.PropertyName == ExtendedEntry.IsBorderErrorVisibleProperty.PropertyName && control != null)
        {
            if (control.IsBorderErrorVisible)
            {
                control.Placeholder = control.ErrorText;
                control.PlaceholderColor = control.BorderErrorColor;
                control.Text = string.Empty;
            }

            else
            {
                control.Placeholder = _placeHolder;
                control.PlaceholderColor = _placeHolderColor;
            }

        }
    }
}

enter image description here

Update:

You can change custom entry's IsBorderErrorVisible in button.click, to call this from submit button.

 private void btn1_Clicked(object sender, EventArgs e)
    {
       
        if(string.IsNullOrEmpty(entry1.Text))
        {
            entry1.IsBorderErrorVisible = true;
        }
    }

<customentry:ExtendedEntry
            x:Name="entry1"
            BorderErrorColor="Red"
            ErrorText="please enter name!">
            <customentry:ExtendedEntry.Behaviors>
                <behaviors:EmptyEntryValidatorBehavior />
            </customentry:ExtendedEntry.Behaviors>
        </customentry:ExtendedEntry>