Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Xamarin entry control TextChanged event looping round

On my form I have 3 entry controls. I'm trying to validate the 'Age' control, with the following validation rules:

  • Cannot enter more than 3 digits

  • Cannot enter a decimal place (.)

  • Cannot enter a hyphen (-)

To do this, I've set the 'TextChanged' property of my control to be

TextChanged="OnAgeTextChanged"

My OnAgeTextChanged method is:

 private void OnAgeTextChanged(object sender, TextChangedEventArgs e)
    {
       var entry = (Entry)sender;

        try
        {

           if (entry.Text.Length > 3)
            {
               string entryText = entry.Text;

               entry.TextChanged -= OnAgeTextChanged;

                entry.Text = e.OldTextValue;
                entry.TextChanged += OnAgeTextChanged;
            }

            string strName = entry.Text;

            if (strName.Contains(".") || strName.Contains("-"))
            {
                strName = strName.Replace(".", "").Replace("-", "");
                entry.Text = strName;
            }
        }

        catch(Exception ex)
        {
            Console.WriteLine("Exception caught: {0}", ex);
        }

    }

However, when the if conditions are met, the event is being looped over multiple times, causing the application to run slowly.

For example, if I enter my age as 1234, it loops over the code multiple times so there's a delay, with the delay increasing each time the text gets changed.

What other way can I achieve this validation, but without the event being called multiple times?

EDIT

After updating the code to remove the TextChanged trigger on my control before re-assigning it at the end of the method, it still loops over multiple times, and the number of loops increases with each key press.

Entry control xaml

<Entry x:Name="txtAge"
       Placeholder="Age"
       Keyboard="Numeric"
       TextColor="DarkBlue"
       PlaceholderColor="DarkBlue"
       Completed="AgeCompleted"
       HorizontalOptions="Start"
       WidthRequest="55"
       TextChanged="OnAgeTextChanged"
/>

TextChanged event

 private void OnAgeTextChanged(object sender, TextChangedEventArgs e)
    {

        var entry = (Entry)sender;

        try
        {

            entry.TextChanged -= OnAgeTextChanged;

            if (entry.Text.Length > 3)
            {

                entry.Text = e.OldTextValue;
            }

            string strName = entry.Text;

            if (strName.Contains(".") || strName.Contains("-"))
            {
                strName = strName.Replace(".", "").Replace("-", "");
                entry.Text = strName;
            }
        }

        catch(Exception ex)
        {
            Console.WriteLine("Exception caught: {0}", ex);
        }

        finally
        {

            entry.TextChanged += OnAgeTextChanged;
        }
     }
like image 803
David Avatar asked Feb 04 '23 23:02

David


1 Answers

The way that I resolved this issue in the end was using a separate class to handle my validations.

My validation class:

using System.ComponentModel;
using System.Runtime.CompilerServices;

public class viewModel : INotifyPropertyChanged
{
 public event PropertyChangedEventHandler PropertyChanged;

 private string age_;
 public string Age { get { return age_; } set { if (age_ != value) { age_ = ProcessAge(value); OnPropertyChanged(); } } }

 private string ProcessAge(string age)
 {
    if (string.IsNullOrEmpty(age))
        return age;

    if (age.Length > 3)
        age = age.Substring(0, 3);

    if (age.StartsWith("0"))
        age = age.Remove(0, 1);

    return age.Replace(".", "").Replace("-", "");
 }

 private void OnPropertyChanged([CallerMemberName] string propertyName = null)
 {
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
 }
}

Then, I can bind the form to use this class, with:

public MainPage()
    {
        InitializeComponent();
        BindingContext = new viewModel();
    }

And finally, to bind the entry control to use the Age property, I set the Text property

Text="{Binding Age, Mode=TwoWay}"

What this now does, is every time the value in the Age control changes, it will look to the Age property in the new class and see that to set it, it needs to go through ProcessAge to validate it, and this is where the checks are now done.

This is faster, as it only occurs once per key press and there's no fiddling around required with subscribing and unsubscribing the TextChanged event and no loops.

like image 159
David Avatar answered Feb 06 '23 14:02

David