[FIXED] Change Entry Text is causing infinite loop in Xamarin

Issue

I need to change the mask and text of an entry depending on its text length. When I change the text, it causes a infinite loop.

See my code:

async void DocNum_TextChanged(System.Object sender, Xamarin.Forms.TextChangedEventArgs e)
{
    string DocNumCopy = Regex.Replace(DocNum.Text,"[^0-9]","");

    if (DocNumCopy.Length > 11)
    {
        if (DocNumMask.Mask != "XX.XXX.XXX/XXXX-XX")
        {
            DocNum.TextChanged -= DocNum_TextChanged;
            DocNum.Text = DocNumCopy;
            DocNumMask.Mask = "XX.XXX.XXX/XXXX-XX";
            DocNum.TextChanged += DocNum_TextChanged;
        }
    }
    else
    {
        if (DocNumMask.Mask != "XXX.XXX.XXX-XX")
        {
            DocNum.TextChanged -= DocNum_TextChanged;
            DocNum.Text = DocNumCopy;
            DocNumMask.Mask = "XXX.XXX.XXX-XX";
            DocNum.TextChanged += DocNum_TextChanged;
        }
    }
}

My Xaml:

<Frame Padding="2" Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2" CornerRadius="5">
    <Entry x:Name="DocNum" Text="" Placeholder="CPF/CNPJ" FontSize="18" TextColor="#8C8C8C" TextChanged="DocNum_TextChanged">
        <Entry.Behaviors>
            <ContentView:MaskedBehavior x:Name="DocNumMask" Mask="XXX.XXX.XXX-XX" />
        </Entry.Behaviors>
    </Entry>
</Frame>

Mask code:

using System.Collections.Generic;
using Xamarin.Forms;

namespace MasterDetailPageNavigation.XAML
{
    public class MaskedBehavior : Behavior<Entry>
    {
        private string _mask = "";
        public string Mask
        {
            get => _mask;
            set
            {
                _mask = value;
                SetPositions();
            }
        }

        protected override void OnAttachedTo(Entry entry)
        {
            entry.TextChanged += OnEntryTextChanged;
            base.OnAttachedTo(entry);
        }

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

        IDictionary<int, char> _positions;

        void SetPositions()
        {
            if (string.IsNullOrEmpty(Mask))
            {
                _positions = null;
                return;
            }

            var list = new Dictionary<int, char>();
            for (var i = 0; i < Mask.Length; i++)
                if (Mask[i] != 'X')
                    list.Add(i, Mask[i]);

            _positions = list;
        }

        private void OnEntryTextChanged(object sender, TextChangedEventArgs args)
        {
            var entry = sender as Entry;

            var text = entry.Text;

            if (string.IsNullOrWhiteSpace(text) || _positions == null)
                return;

            if (text.Length > _mask.Length)
            {
                entry.Text = text.Remove(text.Length - 1);
                return;
            }

            foreach (var position in _positions)
                if (text.Length >= position.Key + 1)
                {
                    var value = position.Value.ToString();
                    if (text.Substring(position.Key, 1) != value)
                        text = text.Insert(position.Key, value);
                }

            if (entry.Text != text)
                entry.Text = text;
        }
    }
}

EDIT1:

DocNum is the document number of the customer. Depending on the number of digits it should have a different mask, if the number has 11 or less chars the mask should be 000.000.000-00 however if it has more than 11 chars the mask should be 00.000.000/0000-00.

DocNumCopy is just a copy of DocNum without the mask, to make easy know the real length of chars.

Solution

Have fixed it by removing the Entry behavior and changing the code of TextChanged by this:

async void DocNum_TextChanged(System.Object sender, Xamarin.Forms.TextChangedEventArgs e)
        {
            var ev = e as TextChangedEventArgs;
            if (ev.NewTextValue != ev.OldTextValue)
            {
                var entry = (Entry)sender;
                string text = Regex.Replace(ev.NewTextValue, @"[^0-9]", "");

                text = text.PadRight(11);

                if(text.Length<=11)
                    text = text.Insert(3, ".").Insert(7, ".").Insert(11, "-").TrimEnd(new char[] { ' ', '.', '-' });
                else if (text.Length > 11)
                {
                    text = text.PadRight(14);
                    text = text.Insert(2, ".").Insert(6, ".").Insert(10, "/").Insert(15, "-").TrimEnd(new char[] { ' ', '.', '-' });
                    if (entry.Text != text)
                        entry.Text = text;
                }

                if (entry.Text != text)
                    entry.Text = text;
            }
}

So if you need to have a different behavior for a specific entry, maybe the simplest way is doing something like this.

Answered By – Éder Rocha

Answer Checked By – Gilberto Lyons (Easybugfix Admin)

Leave a Reply

(*) Required, Your email will not be published