Xamarin Binding to complex bool

LippelLippel DEMember ✭✭
edited October 13 in Xamarin.Forms

Hello,

I want to bind an IsVisible property to a bool with a complex getter.
How am I supposed to notify the view if this getter should be called again?

Pseudo Code:

List<int> ListOfNumbers;
public bool HasUnevenElements
        {
            get { return ListOfNumbers.Any(x => x == IsUneven(x)); }
        }

private bool IsUneven(int number){
//returns true/false wheter number is uneven or not
}

How to implement the INPC here?

Here I want to bind for example like this:

<Label Text="This list has uneven elements" IsVisible="{Binding HasUnevenElements}" />

Best Answer

  • LippelLippel DEMember ✭✭
    Accepted Answer

    I have solved it with another solution, which seems to be the best practice in this case:

    My models all implement INPC.
    In my ViewModel:
    When I add a model to the list, the ViewModel subscribes to the models PropertyChanged event.

    Example ViewModel:

    private ObservableCollection<NumberItem> numbersList;
    public ObservableCollection<NumberItem> NumbersList{ get { return numbersList; } set { SetProperty(ref numbersList, value); } }
    private string listDescrText;
    
            public string ListDescrText
            {
                get { return listDescrText; }
                set { SetProperty(ref listDescrText, value); }
            }
    
    public MainVM()
            {
                NumbersList= new ObservableCollection<NumberItem>();
    
                for (int i = 0; i < 3; i++)
                {
                    var nItem = new NumberItem
                    {
                        Text = $"My number is: { Number }",
                Number = i,
                    };
            //Subscribe here, so the VM gets notified when a property of an element in the list changes
                    nItem.PropertyChanged += NumberItem_PropertyChanged;    
    
                    NumbersList.Add(nItem );
                }
            }
    
            private void NumberItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
            //Here you can check if the specific property on an element has changed and react to it
                if (e.PropertyName == nameof(NumberItem.Number))
                    CheckIfListHasUnevens();
            }
    
    void CheckIfListHasUnevens(){
        if(NumbersList.Any(x => x.Number.Uneven()){
            ListDescrText = "List has uneven numbers";
     else
         ListDescrText = "List has no uneven numbers";
     }
    }
    

Answers

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    If you're looking to bind then don't make a complete additional property for it. Make a converter.
    Then when the number property changes that raises an event, and the UI binding uses a converter for the `IsUneven'.
    That way the converter can be applied to any number of numeric values.

    Converter

        public class IsOddConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                if (value == null)
                    return false;
    
                double temp = 0d;
                if (!double.TryParse(value.ToString(), out temp)) return false;
                return temp % 2 == 0;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
    

    Number Property

            #region SomeNumber (decimal)
            private decimal _SomeNumber;
            public decimal SomeNumber
            {
                get
                {
                    return _SomeNumber;
                }
    
                set
                {
                    if (_SomeNumber == value) return;
                    _SomeNumber = value;
                    OnPropertyChanged();
                }
            }
            #endregion SomeNumber  (decimal)
    

    XAML Page

        <Application.Resources>
            <ResourceDictionary>
                <converters:IsOddConverter x:Key="IsOddConverter" />
    {...}
            <Label Text="{Binding SomeNumber, Converter={StaticResource IsOddConverter}}"></Label>
    
  • LippelLippel DEMember ✭✭
    edited October 13

    Sorry, I think you missunderstood.

    I don't want to convert a single number to a bool, based on the fact if its uneven or not.
    I want to bind a whole list of numbers to a bool, that sets true if there are uneven numbers in it and false if there are no uneven numbers.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    Then you really don't want to make all those properties. That's just making ti more convoluted
    Make a converter to return whether or not the list collection has an odd number of elements.

        public class IsOddCountConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                if (value == null)
                    return false;
    
                var count = (value as ICollection)?.Count;
                return count % 2 == 0;
    
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
    

    And send the entire collection to the converter

  • LippelLippel DEMember ✭✭

    You are probably right!
    Even tho in your List Converter you are checking if the Count of the List is uneven.
    But i actually want to check if the List has Elements of int that are uneven.
    But no problem, i will try it out and come back.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    Ah... I missed that detail. But sounds like you're on your way. Cool.

  • LippelLippel DEMember ✭✭
    edited October 13

    Ok I hit the big issue I've already hit multiple times.
    Now that the Elements (the numbers) can change, but the List.Count can remain the same:
    No INPC will be called for this, so the value converter will not receive anything.

    What would you suggest to call the Lists setter to make an INPC call and therefore check, if there are uneven elements in the list?

    So when a number in the list changes, the converter should check again if there are uneven elements, and return a bool based on this

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    OnCollectionChanged event

  • LippelLippel DEMember ✭✭
    edited October 13

    No you dont get my point.
    The bool ListHasUnevenElements should be updated when an Element in the List<int> has changed.

    OnCollectionChanged is fired, when the List<int>.Count has changed

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    No INPC will be called for this, so the value converter will not receive anything.

    Not OnPropertyChanged... OnCollectionChanged... for when elements in the property have changed.

    OnCollectionChanged fires when the collection changes... Such as elements added, removed. You said you needed to trigger when the elements change.

    No you dont get my point.
    The bool ListHasUnevenElements should b

    You're not getting my point - Don't make properties just for things like this. Its problematic keeping everything in sync. If you had 20 collections would you want 20 bools to go along with them?

    One converter that takes any collection and returns a bool amount means you can reuse that bit of code.

    But... You're not getting that... So let's go plan B.
    Just raise a notificaiton that your collection or property has changed. You can throw that event whenever you like and have all the subscribers change.

    public someMethod()
    {
       //Do some other stuff.... get new values... call services... whatever...
       OnPropertyChange("MyCollection");//Tell the world the collection changed and let everyone else react
    }
    
  • LippelLippel DEMember ✭✭
    edited October 13

    But the list doesnt change... the elements inside the list change

    Another example:

    View:
    <Label Text={Binding ListElementString} />

    ViewModel:

    List<int> Numbers = new List<int>() { 1,2,3 };
    
    public string ListElementString
    {
    get { return string.Format("You have {0} uneven and {1} even Elements in the List", Numbers.Where(x => x == Uneven), Numbers.Where(x => x == Even)); }
    }
    

    So the label would show:
    "You have 2 uneven and 1 even Elements in the List"

    Now I do this:
    Numbers[3] = 4;

    Then the label should be updated to show:
    "You have 1 uneven and 2 even Elements in the List"

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    But the list doesnt change... the elements inside the list change

    So what? You said you just want some trigger to cause everything to recalculate.
    When you update some value inside the collection, raise the notifcation that that the property has changed. That will cause every binding to the collection to re-evalute.

  • LippelLippel DEMember ✭✭
    Accepted Answer

    I have solved it with another solution, which seems to be the best practice in this case:

    My models all implement INPC.
    In my ViewModel:
    When I add a model to the list, the ViewModel subscribes to the models PropertyChanged event.

    Example ViewModel:

    private ObservableCollection<NumberItem> numbersList;
    public ObservableCollection<NumberItem> NumbersList{ get { return numbersList; } set { SetProperty(ref numbersList, value); } }
    private string listDescrText;
    
            public string ListDescrText
            {
                get { return listDescrText; }
                set { SetProperty(ref listDescrText, value); }
            }
    
    public MainVM()
            {
                NumbersList= new ObservableCollection<NumberItem>();
    
                for (int i = 0; i < 3; i++)
                {
                    var nItem = new NumberItem
                    {
                        Text = $"My number is: { Number }",
                Number = i,
                    };
            //Subscribe here, so the VM gets notified when a property of an element in the list changes
                    nItem.PropertyChanged += NumberItem_PropertyChanged;    
    
                    NumbersList.Add(nItem );
                }
            }
    
            private void NumberItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
            //Here you can check if the specific property on an element has changed and react to it
                if (e.PropertyName == nameof(NumberItem.Number))
                    CheckIfListHasUnevens();
            }
    
    void CheckIfListHasUnevens(){
        if(NumbersList.Any(x => x.Number.Uneven()){
            ListDescrText = "List has uneven numbers";
     else
         ListDescrText = "List has no uneven numbers";
     }
    }
    
  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    which seems to be the best practice in this case:

    I'm glad you got something working that gets your app going. But rigging up event subscriptions to a method to purpose-build properties, just to get a bool as to whether or not the collection contains an odd value... Is far from what would be called "best practices". Its a good work-around. It gets the job done. It resolves the immediate issue that was holding you up. But its brute force.

  • LippelLippel DEMember ✭✭

    What would be the biggest downsides to this?
    Memory leaks due to missing unsubscribing of the events?

    How else could this be handled? I dont see a better idea right now, and didnt u suggest something pretty similiar?

    "Just raise a notificaiton that your collection or property has changed. You can throw that event whenever you like and have all the subscribers change."

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    If you are creating specific methods to handle one property... and creating specific properties just to use for the binding of the bool... Then you know you're going off track.

    A converter is reusable for countless collection properties without having all the specific tightly dependent supporting methods and properties.

    Subscribing to property changed... Again, not needed if you are binded to the property and have a converter. You just raise the notification that the collection has changed... Period. Stop... Done. The UI is binded t the collection through the converter. If the UI gets told the collection has changed then it will automatically re-run the converter using the collection as the parameter and update the UI with the new result.

    I'll see if I can't put together a walk-through for my site using this concept as the topic. I probably won't get the free time for it until the weekend though.

  • LippelLippel DEMember ✭✭

    But i think in my situation this is too complex for a converter.
    The list holds items that have 2 enums.
    Based on the 2 enum values of each item, i have to evaluate if any of 3 buttons have to be shown.
    And if an enum value of one item changes, i have to keep the ui updated.

  • LippelLippel DEMember ✭✭

    You say "you just raise the notification that the collection has changed".
    How would an element in the collection do this?

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    I already pointed out the OnCollectionChanged event.
    Now this is just going in circles. I'll put together an article this weekend.

  • LippelLippel DEMember ✭✭

    Still, when i read about "OnCollectionChanged" it always affects removing/adding elements to a list. thats not what i need

Sign In or Register to comment.