Forum Xamarin.Forms

TextChanged event looping round multiple times

DavidH_1997DavidH_1997 Member ✭✭
edited June 2018 in Xamarin.Forms

I've created an Entry control using xaml as below

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

When typing, the OnAgeTextChanged event it validation the text entered into the control.

private void OnAgeTextChanged(object sender, TextChangedEventArgs e)
{
// Creates a new instance of the age text box
var entry = (Entry)sender;

        try
        {
            // Removes the TextChanged property from the entry control
            entry.TextChanged -= OnAgeTextChanged;

            // if the newly-add character makes the length > 3
            if (entry.Text.Length > 3)
            {
                // Updates the entry to revert back to the 3 digit string
                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
        {
            // Re-assigns the TextChanged method to the entry control
            entry.TextChanged += OnAgeTextChanged;
        }
     }

However, it appears that the more times I type into the control, the more loops it appears to do.

As an example:
Typing "1" into the control - 1 loop, which is fine.
Typing "2", to make "12" - 2 loops
Typing "3", making "123" - 4 loops
Typing "4", to check it remains at "123" - 24 loops
Typing "5", to check it remains at "123" - Lots of loops, I believe there was over 90!

Why is this happening? I'm not sure, as I'm removing the event handler and re-assigning AFTER I've updated the value. Has anyone got any ideas?

Best Answer

Answers

  • seanydaseanyda GBMember ✭✭✭✭✭
    edited June 2018

    I think this is because on the TextChanged event you are running logic to update the text which is making it fire again.

    entry.Text = e.OldTextValue; // this will fire the event
    entry.Text = strName; // and this will
    

    EDIT : Sorry just noticed you have removed the event handler.. Just out of curiosity does the same amount of loops occur when removing the lines I commented above? Could be a forms issue removing the event handler??

  • DavidH_1997DavidH_1997 Member ✭✭

    @seanyda said:
    I think this is because on the TextChanged event you are running logic to update the text which is making it fire again.

    entry.Text = e.OldTextValue; // this will fire the event
    entry.Text = strName; // and this will
    

    But I've removed the TextChanged event at the start so I'd not expect it to be getting called at those points?

  • seanydaseanyda GBMember ✭✭✭✭✭

    @DavidH_1997 said:

    @seanyda said:
    I think this is because on the TextChanged event you are running logic to update the text which is making it fire again.

    entry.Text = e.OldTextValue; // this will fire the event
    entry.Text = strName; // and this will
    

    But I've removed the TextChanged event at the start so I'd not expect it to be getting called at those points?

    Sorry I just updated my post when I saw that. What version of forms is being used?

  • DavidH_1997DavidH_1997 Member ✭✭
    edited June 2018

    @seanyda said:

    @DavidH_1997 said:

    @seanyda said:
    I think this is because on the TextChanged event you are running logic to update the text which is making it fire again.

    entry.Text = e.OldTextValue; // this will fire the event
    entry.Text = strName; // and this will
    

    But I've removed the TextChanged event at the start so I'd not expect it to be getting called at those points?

    Sorry I just updated my post when I saw that. What version of forms is being used?

    If I comment out those lines, it's the exact same result as I posted originally, in terms of the loops cycle. The lines you mentioned aren't being hit though, until the 4th key press (unless that key press is a backspace) so these shouldn't be the problematic lines.

    EDIT: How do I find out the version of Forms?

  • Amar_BaitAmar_Bait DZMember ✭✭✭✭✭
    edited June 2018

    That's why people should use MVVM + Fody.PropertyChanged. With data binding, you won't have these problems, just update your property and boom it reflects back.

    <Entry
           Placeholder="Age"
           Keyboard="Numeric"
           TextColor="DarkBlue"
           PlaceholderColor="DarkBlue"
           HorizontalOptions="Start"
           WidthRequest="55"
           Text="{Binding Age, Mode=TwoWay}"
          />
    
    public class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        // backing field and invoking PropertyChanged not needed, courtesy of Fody.PropertyChanged!
        public string Age { get; set; }
    
    
        // gets called automatically when Age changes, courtesy of Fody.PropertyChanged!
        private void OnAgeChanged()
        {
            if (string.IsNullOrEmpty(Age))
                return;
    
            var age = Age;
    
    
            if (age.Length > 3)
                age = age.Substring(0, 3);
    
            Age = age.Replace(".", "").Replace("-", "");  // no need to check if age contains . or -
        }
    }
    
  • DavidH_1997DavidH_1997 Member ✭✭

    @nadjib said:
    That's why people should use MVVM + Fody.PropertyChanged. With data binding, you won't have these problems, just update your property and boom it reflects back.

    <Entry
           Placeholder="Age"
           Keyboard="Numeric"
           TextColor="DarkBlue"
           PlaceholderColor="DarkBlue"
           HorizontalOptions="Start"
           WidthRequest="55"
           Text="{Binding Age, Mode=TwoWay}"
          />
    
    public class ViewModel : INotifyPropertyChanged
    {
      public event PropertyChangedEventHandler PropertyChanged;
    
      // backing field and invoking PropertyChanged not needed, courtesy of Fody.PropertyChanged!
      public string Age { get; set; }
      
    
      // gets called automatically when Age changes, courtesy of Fody.PropertyChanged!
      private void OnAgeChanged()
      {
          if (string.IsNullOrEmpty(Age))
              return;
    
          var age = Age;
    
    
          if (age.Length > 3)
              age = age.Substring(0, 3);
    
          Age = age.Replace(".", "").Replace("-", "");  // no need to check if age contains . or -
      }
    }
    

    Thanks for this, it certainly seems like an easier way to do it - I've created the ViewModel class exactly as you have, (plus adding: using ComponentModel), as well as updated the property in xaml. but the event is being called? I've put a breakpoint on, but it's not being hit.

  • seanydaseanyda GBMember ✭✭✭✭✭

    @DavidH_1997 said:

    @nadjib said:
    That's why people should use MVVM + Fody.PropertyChanged. With data binding, you won't have these problems, just update your property and boom it reflects back.

    <Entry
           Placeholder="Age"
           Keyboard="Numeric"
           TextColor="DarkBlue"
           PlaceholderColor="DarkBlue"
           HorizontalOptions="Start"
           WidthRequest="55"
           Text="{Binding Age, Mode=TwoWay}"
          />
    
    public class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        // backing field and invoking PropertyChanged not needed, courtesy of Fody.PropertyChanged!
        public string Age { get; set; }
        
    
        // gets called automatically when Age changes, courtesy of Fody.PropertyChanged!
        private void OnAgeChanged()
        {
            if (string.IsNullOrEmpty(Age))
                return;
    
            var age = Age;
    
    
            if (age.Length > 3)
                age = age.Substring(0, 3);
    
            Age = age.Replace(".", "").Replace("-", "");  // no need to check if age contains . or -
        }
    }
    

    Thanks for this, it certainly seems like an easier way to do it - I've created the ViewModel class exactly as you have, (plus adding: using ComponentModel), as well as updated the property in xaml. but the event is being called? I've put a breakpoint on, but it's not being hit.

    You need to set the BindingContext of the ContentPage to point towards the ViewModel too.

  • DavidH_1997DavidH_1997 Member ✭✭
    edited June 2018

    @seanyda said:
    You need to set the BindingContext of the ContentPage to point towards the ViewModel too.

    What's the correct way of doing this? I've tried as below but that didn't work

    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MyApplication"
             x:Class="MyApplication.MainPage"
         BindingContext="ViewModel.cs">
    
  • Amar_BaitAmar_Bait DZMember ✭✭✭✭✭
    edited June 2018

    Different ways, easiest is to set it in page code-behind constructor

    InitializeComponent();
    BindingContext = new ViewModel();
    

    or in XAML (you need to declare xmlns:viewModels)

    <ContentPage.BindingContext>
        <viewModels:ViewModel/>
      </ContentPage.BindingContext>
    

    or when navigating to the page:

    Navigation.PushAsync(new MainPage { BindingContext = new ViewModel() });
    
    // if it's the main page in App.xaml.cs:
    // MainPage = new MainPage { BindingContext = new ViewModel() };
    
    

    Also make sure you installed Fody.PropertyChanged package (install it only in PCL/Net Standard project) and don't forget to create a FodyWeavers.xml file with this content:

    <?xml version="1.0" encoding="utf-8" ?>
    <Weavers>
      <PropertyChanged/>
    </Weavers>
    
  • DavidH_1997DavidH_1997 Member ✭✭

    @nadjib said:
    Also make sure you installed Fody.PropertyChanged package (install it only in PCL/Net Standard project) and don't forget to create a FodyWeavers.xml file with this content:

    <?xml version="1.0" encoding="utf-8" ?>
    <Weavers>
      <PropertyChanged/>
    </Weavers>
    

    I think I've misunderstood this part. I've not downloaded anything from the link that you sent, I've just run the command in the Package Manager to install the latest version of it, then put your code into my project and added the files you've mentioned - is there something I've done wrong or missed, as it still isn't working?

  • seanydaseanyda GBMember ✭✭✭✭✭

    @DavidH_1997 said:

    @nadjib said:
    Also make sure you installed Fody.PropertyChanged package (install it only in PCL/Net Standard project) and don't forget to create a FodyWeavers.xml file with this content:

    <?xml version="1.0" encoding="utf-8" ?>
    <Weavers>
      <PropertyChanged/>
    </Weavers>
    

    I think I've misunderstood this part. I've not downloaded anything from the link that you sent, I've just run the command in the Package Manager to install the latest version of it, then put your code into my project and added the files you've mentioned - is there something I've done wrong or missed, as it still isn't working?

    The Fody.PropertyChanged is optional. You can do what you're trying to achieve in standard MVVM. I would suggest reading through here to get a better understanding.

    https://docs.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-basics/data-binding-basics

  • DavidH_1997DavidH_1997 Member ✭✭

    @seanyda said:
    The Fody.PropertyChanged is optional. You can do what you're trying to achieve in standard MVVM. I would suggest reading through here to get a better understanding.

    https://docs.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-basics/data-binding-basics

    Sorry, I'm incredibly confused - this is my first Xamarin project so I'm still trying to get to grips with it all.

    I've taken out everything that I'd added based on this thread so far, so that there were no bindings, the view model class had gone etc.

    I've taken the following steps since:

    1) In MainPage.xaml.cs, I've added one line, to make

     InitializeComponent();
     BindingContext = new ViewModel();
    

    2) That currently has an error, as it has no idea what ViewModel is, so I've added a class "viewModel.cs", which contains the following code

    using System.ComponentModel;
    
    public class ViewModel : INotifyPropertyChanged
    {
    public event PropertyChangedEventHandler PropertyChanged;
    
    public string Age { get; set; }
    
    private void OnAgeChanged()
    {
        if (string.IsNullOrEmpty(Age))
            return;
    
        var age = Age;
    
        if (age.Length > 3)
            age = age.Substring(0, 3);
    
        Age = age.Replace(".", "").Replace("-", "");
     }
    }
    

    3) In my MainPage.xaml file, I've taken out the TextChanged property from the Entry control, and removed the OnAgeTextChanged method from MainPage.xaml.cs, and instead set the Text property

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

    This isn't triggering the OnAgeChanged method in the viewModel.cs class, but when the app first runs, it does hit the public string Age { get; set; } line, but doesn't hit it again.

    I've obviously misunderstood the article in the quoted post, so I'm guessing something in step 2 is wrong, but what is it that I'm doing wrong? Again, my apologies for not being quick at picking this up.

  • DavidH_1997DavidH_1997 Member ✭✭

    @nadjib that is perfect, thank you!

  • DavidH_1997DavidH_1997 Member ✭✭

    @nadjib said:

    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);
    
          return age.Replace(".", "").Replace("-", "");  // no need to check if age contains . or -
      }
    
    // this method will notifiy your UI (control) that the property has changed, so it displays the new value
        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
               PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    

    I've used this same format and modified it slightly to also create validation rules for names, as below:

    private string ProcessName(string name)
    {
        if (string.IsNullOrEmpty(name))
            return name;
    
        if (name.Length > 20)
            name = name.Substring(0, 20);
    
        return name;
    }
    

    The final validation rule that I need is to only allow letters, spaces, or a hyphen (-), apostrophe ( ' ) or a full stop ( . )

    What's the easiest way to do this?

  • Amar_BaitAmar_Bait DZMember ✭✭✭✭✭
Sign In or Register to comment.