0

I have a collection that gets populated with produce. Once the collection is populated, a BindableLayout/DataTemplate bound to a StackLayout will display the items and the user will be prompted with the option to change the Price property of a stock item by typing into an Entry.

A user can type into the provided Entry box to change Price property of each StockInfo object in the collection, and the change WILL SUCCESSFULLY be applied to the Observable Collection, BUT it WILL NOT fire the setter/property changed event of the Observable Collection.

I need the property changed event to fire so that I can effectively execute other parts of my code, but since it won't the fire setter or property changed of the collection, it never gets the chance to tell other parts of my code to do things.

namespace Test
{
    public class Testing : BaseContentPage, INotifyPropertyChanged
    {
        public class StockInfo : BaseContentPage, INotifyPropertyChanged
        {
            private string description;
            public string Description
            {
                get => description;
                set
                {
                    description = value; 
                    OnPropertyChanged();
                }
            }

            private int price;
            public int Price
            {
                get => price;
                set
                {
                    price = value; 
                    OnPropertyChanged();
                }
            }
        }

        private ObservableCollection<StockInfo> stockItems = new ObservableCollection<StockInfo>();
        public ObservableCollection<StockInfo> StockItems
        {
            get => stockItems;
            set
            {
                stockItems = value;
                OnPropertyChanged();
                OnPropertyChanged("SumPrices");
            }
        }

        public double SumPrices
        {
            get
            {
                return StockItems.Sum(p => p.Price);
            }
        }

        DataTemplate StockTemplate = new DataTemplate(() =>
        {
            return new StackLayout
            {
                Orientation = StackOrientation.Horizontal,

                Children =
                {
                    new Entry
                    {
                    }.Bind(Entry.TextProperty, path: "Description")

                    ,

                    new Entry
                    {
                        Keyboard = Keyboard.Numeric
                    }.Bind(Entry.TextProperty, path: "Price")
                }
            };
        });


        public Testing()
        {
            BindingContext = this;

            StockItems.Add(new StockInfo { Description = "Milk", Price = 20 });
            StockItems.Add(new StockInfo { Description = "Cheese", Price = 15 });

            Content = new StackLayout
            {
                Children =
                {
                    new StackLayout
                    {
                    }.Invoke(layout => BindableLayout.SetItemTemplate(layout, StockTemplate))
                    .Bind(BindableLayout.ItemsSourceProperty, path: "StockItems")

                    ,

                    new Label
                    {
                    }.Bind(Label.TextProperty, path: "SumPrices")
                }
            };
        }
    }
}

If I put a debugger stop line inside the get/set of the "Description" property in the StockInfo class and then type in the Entry, the debugger will pick it up and stop the program for debugging.

But if I put a debugger stop on a line some where in the set/get of the Observable Collection, the program will not stop inside of it.

*** Edits Below ***

I modified the code so that StockInfo now has a property that includes the price of a product. I also added a variable called SumPrices which will return the Sum of Price within StockItems using LINQ. The first time the page loads, the sum is calculated and the result is correct, but if I change the Entry box that the property is bound to for each object, it has no effect and the SumPrices variable never changes.

Ideally, I'd simply like for the Observable Collection to fire its setter/property change events whenever an Object's property within the collection is changed.

1 Answers1

1

New Update Here

You cannot fire the setter of ObservableCollection when a property of an item in this collection has changed. I've searched so many info from the Internet and found a question similar to yours: ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged). Bob Sammers abstract and define a new FullyObservableCollection class and put forward a pretty robust solution, including some of the techniques in other answers. This new class could get notified when a property of item has been changed. I have tested it and worked well.

Simply used it like the following code:

private FullyObservableCollection<StockInfo> stockItems = new FullyObservableCollection<StockInfo>();
public FullyObservableCollection<StockInfo> StockItems
{
    get => stockItems;
    set
    {
        stockItems = value;
        OnPropertyChanged();
    }
}

public Testing ()
    {
        ...
        StockItems.ItemPropertyChanged += StockItems_ItemPropertyChanged;
        ...
    }

    private void StockItems_ItemPropertyChanged(object sender, ItemPropertyChangedEventArgs e)
    {
        OnPropertyChanged(nameof(SumPrices));
    }

Another workaround is what i've suggested in my previous answer, which is using TextChanged event handler.

In Datatemplate, add an EventHandler for entry:

new Entry
{
}.Bind(Entry.TextProperty, path: "Description",BindingMode.TwoWay)
.Invoke(entry=>entry.TextChanged+=Entry_TextChanged)


private void Entry_TextChanged(object sender, TextChangedEventArgs e)
    {
        OnPropertyChanged(nameof(SumPrices));
    }

For more info, you could refer to Xamarin.Forms - CollectionView sample

Hope it works for you.

Liqun Shen-MSFT
  • 3,490
  • 2
  • 3
  • 11
  • Hi, thanks for answering :) ... I've messed around with the CollectionChanged event, it seemed like a good solution at first, but I found out that the event won't fire when properties of objects in the collection are changed. It only fires when the entire collection is manipulated (Something gets added/deleted, etc.). I'm looking for a solution that reacts to the properties of the observable collection's objects being changed. – chiken nuget Jan 05 '23 at 14:55
  • The most easiest way is what i mentioned in my answer. You could use a middleware such as a List. When you change the property of object in collection, first change the List, then assign the List to collection. – Liqun Shen-MSFT Jan 06 '23 at 00:35
  • Actually, i cannot give you more suggestion as it seems that you change the item in collection in xaml i guess. So if you could show me the xaml or the way how you bind, i think that might be helpful . – Liqun Shen-MSFT Jan 06 '23 at 01:04
  • I actually replace XAML by using Xamarin Community Toolkit Markup: https://learn.microsoft.com/en-us/xamarin/community-toolkit/markup What is shown on screen is what is inside of "Content =" within the RecordStock class. I bind a DataTemplate to the main StackLayout and so the only thing that shows on the screen are entries for each "StockInfo" object in the collection. – chiken nuget Jan 06 '23 at 01:10
  • I see. When user enter the text in entry that changes the description of stockInfo in Collection. Then you want to fire the setter of StockItems, right? – Liqun Shen-MSFT Jan 06 '23 at 01:30
  • i update my answer. I think you could use Entry.textchanged eventhandler. You could add some logic. – Liqun Shen-MSFT Jan 06 '23 at 05:59
  • My current implementation actually fires the setter of StockItems already without having to use the TextChanged event handler (It automatically occurs since the description property is bound to the entry). I will update my answer tomorrow with more details so that I may be able to express what I am trying to do a bit better :). I realize now that what I wrote doesn’t capture the whole picture because ultimately I’m trying to get the .Sum() of an object property from the entire collection. – chiken nuget Jan 06 '23 at 06:34
  • I slightly edited my question to better represent what my issue is. Even though the StockInfo's property for Price is bound to an Entry and OnPropertyChanged("SumPrices"); is in the setter of StockItems, the Label where SumPrices is bound to never updates. – chiken nuget Jan 06 '23 at 13:38
  • I have updated my answer. You could have a try. – Liqun Shen-MSFT Jan 07 '23 at 06:31
  • I understand. I've tried this implementation in the past but it didn't work. I will try it again because I may have made a mistake somewhere. – chiken nuget Jan 08 '23 at 03:27
  • Ok, seems like that FullyObservableCollection solution did work, thanks! I do have one more question though in regards to the Entry.TextChanged. Since the Invoke is inside of the DataTemplate, how would I get that to talk to a method outside of the DataTemplate and therefore 'talk to' the SumPrice property? It forces me to create a Static method, but then that won't recognize SumPrice when I try to use it. – chiken nuget Jan 09 '23 at 13:52
  • You could put DataTemplate in your Testing page constructor. – Liqun Shen-MSFT Jan 09 '23 at 15:45