Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XAML - IValueConverter and Behavior conflict with each other causing an endless loop to occur

With a Xamarin Forms application, I have an IValueConverter and Behavior that conflict with each other causing an endless loop to occur. I have created a simple application that demonstrates this problem that can be downloaded (link below), and I have included the relevant code below.

Here are the requirements I am trying to achieve in this scenario.

  1. The user must be able to enter an empty value for the int.
  2. The user is only allowed to enter an integer value.

For #1, I use a nullable int in the backend model. If I were to use just an 'int', then the field would always end up with a '0' in it if it were cleared. So, the IValueConverter implementation StringToIntConverter is used to convert the value from a string to an int, and if an empty string is passed, the property is set to null.

For #2, the Behavior IntegerValidationBehavior inspects each keystroke and eliminates any non-integer values including periods. In addition, for this example, I only show the numeric keyboard. However, it allows some non-integer characters like the period, so the IntegerValidationBehavior is needed.

For normal inputs it works great. But if you start with a '0' and then enter another number, it goes haywire ending up in an endless loop. I have verified this on various XF versions as well as both iOS and Android platforms.

How would I change the code to meet my requirements?

Steps to Reproduce

  1. Run the demo as found in the github repo below
  2. Enter '05' into the input box and app freezes in an endless loop

Reproduction Link

https://github.com/JohnLivermore/SampleXamarinApp/tree/endlessloop


IntegerValidationBehavior

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

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

    private static void OnEntryTextChanged(object sender, TextChangedEventArgs args)
    {
        if (!string.IsNullOrWhiteSpace(args.NewTextValue))
        {
            //make sure all characters are numbers
            var isValid = args.NewTextValue.ToCharArray().All(x => char.IsDigit(x));

            ((Entry)sender).Text = isValid ? args.NewTextValue : args.NewTextValue.Remove(args.NewTextValue.Length - 1);
        }
    }
}

StringToIntConverter

public class StringToIntConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null)
            return "";
        else
            return ((int)value).ToString();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var val = value as string;

        if (string.IsNullOrWhiteSpace(val))
            return null;
        else
        {
            var result = 0;
            int.TryParse(val, out result);
            return result;
        }
    }
}

XAML

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:behaviors="clr-namespace:SampleApp"
             mc:Ignorable="d"
             x:Class="SampleApp.MainPage">

    <StackLayout>
        <Entry Keyboard="Numeric"
               Text="{Binding Model.Length, Mode=TwoWay, Converter={StaticResource StringToInt}}">
            <Entry.Behaviors>
                <behaviors:IntegerValidationBehavior />
            </Entry.Behaviors>
        </Entry>
        <Label Text="{Binding Model.LengthString}"
               TextColor="Black" />
        <Button Text="Process"
                Command="{Binding Process}" />
    </StackLayout>

</ContentPage>

Model

public class MainPageModel : FreshBasePageModel
{
    public MainPageModel()
    {
        Model = new Model();
    }

    public Model Model { get; set; }
}

public class Model : INotifyPropertyChanged
{
    private int? _length;

    public int? Length
    {
        get { return _length; }
        set { SetProperty(ref _length, value); }
    }

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

    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(storage, value))
        {
            return false;
        }
        storage = value;
        OnPropertyChanged(propertyName);

        return true;
    }
    public event PropertyChangedEventHandler PropertyChanged;
}
like image 908
John Livermore Avatar asked Nov 25 '25 04:11

John Livermore


1 Answers

Replace below methode with your OnEntryTextChanged Method in IntegerValidationBehavior file and check it's worked.

private static void OnEntryTextChanged(object sender, TextChangedEventArgs args)
            {
                if (!string.IsNullOrWhiteSpace(args.NewTextValue))
                {

                    //make sure all characters are numbers
                    var isValid = args.NewTextValue.ToCharArray().All(x => char.IsDigit(x));

                    if (isValid && args.NewTextValue.Length > 1 && args.NewTextValue.StartsWith("0"))
                        return;

                    ((Entry)sender).Text = isValid ? args.NewTextValue : args.NewTextValue.Remove(args.NewTextValue.Length - 1);
                }
            }
like image 117
Pratik Avatar answered Nov 27 '25 19:11

Pratik



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!