How can I bind to a property value within a an object in a grouped list

Viewed 44

First off, thankyou for the solution to my previous question here

That solution works very well, however, i'm struggling to bind the change of one of the properties. I have the BackgroundColour binding well when the UI loads with the following code: UI View

<StackLayout x:Name="stlShippingOrders" Spacing="5" Padding="0,0,0,5" >
        <CollectionView Margin="0,-6,0,0" IsGrouped="True" ItemsSource="{Binding 
ShippingItems}">
            <CollectionView.GroupHeaderTemplate>
                <DataTemplate>
                    <StackLayout Padding="5,-5,5,-5" Orientation="Horizontal" 
BackgroundColor="LightBlue" HeightRequest="45">
                        <Label Text="{Binding Shipment.OrderCode}" 
VerticalOptions="Center"/>
                        <Label Text="{Binding Shipment.CustomerName}" 
VerticalOptions="Center"/>
                        <Label Text="{Binding Shipment.DeliveryDate}" 
HorizontalOptions="EndAndExpand" VerticalOptions="Center" />
                    </StackLayout>
                </DataTemplate>
            </CollectionView.GroupHeaderTemplate>
            <CollectionView.ItemTemplate>
                <DataTemplate>
                    <Label Padding="5,2,5,2" Text="{Binding ItemDescription}" 
BackgroundColor="{Binding BackgroundColour}"/>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </StackLayout>

ShippingItem Model (with added BacgroundColour property)

 public class ShippingItem
{
    public String ShippingItemId { get; set; }
    public string ShippingItemScanCode { get; set; }
    public string ItemDescription { get; set; }
    public string ShippingOrderId { get; set; }
    public bool IsLoaded { get; set; }

    // Default set to transparent #00FFFFFF
    public string BackgroundColour { get; set; }  = "#00FFFFFF";

}

Class for grouping

public class ShippingItemGroup : ObservableCollection<ShippingItem>
{
    public ShippingOrder Shipment { get; private set; }

    public ShippingItemGroup(ShippingOrder shipment, ObservableCollection<ShippingItem> 
shippingItems) : base(shippingItems)
    {
        Shipment = shipment;
    }
}

ViewModel code that creates the group list and binds correctly

                        public ObservableCollection<ShippingItemGroup> ShippingItems { 
get; private set; } = new ObservableCollection<ShippingItemGroup>();
                    foreach (var item in thisShipment.ShippingOrders)
                    {
                        ShippingItems.Add(new ShippingItemGroup(item, 
item.ShippingItems));
                    }

ViewModel code that updates the list object when it is loaded. This is the bit i'm having trouble with, the shippingItem.BackgroundColour = "FFA533" changes the collection but doesn't update the UI.

foreach (ShippingItemGroup shippingItemGroup in ShippingItems)
                    {
                        foreach(ShippingItem shippingItem in 
shippingItemGroup.Shipment.ShippingItems)
                        {
                            if(shippingItem.ShippingItemScanCode == scanCode)
                            {
                                shippingItem.IsLoaded = true;
                                shippingItem.BackgroundColour = "FFA533";
                                IsShipmentHeaderVisible = true;
                                IsShippingOrdersVisible = true;
                                IsErrorMessageVisible = false;
                                ScanCode = "";
                            }
                        }
                    }
1 Answers

As Jason mentioned in the comments, you need to implement INotifyPropertyChanged. This is the way I've done it for other projects. This is adapted from how MVVMCross does it.

First, create a base class where the INotifyPropertyChanged logic is setup:

public class ModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

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

        storage = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        return true;
    }
}

This will handle setting the value and firing the PropertyChanged event.

Next, have whatever model you need inherit from this base class. In this, it's your ShippingItem class:

public class ShippingItem : ModelBase
{
    public int Id { get; set; }
    public int ShippingOrderId { get; set; }
    public string ItemDescription { get; set; }
    public bool IsLoaded { get; set; }

    private string backgroundColor;
    public string BackgroundColor
    {
        get => backgroundColor;
        set => SetProperty(ref backgroundColor, value);
    }
}

Note the addition of the private backgroundColor field, and the custom setter that calls the SetProperty in the base class.

This will handle the updates for you.

I modified the code from the previous answer just as a proof of concept. This will update the colors every 1.5 seconds:

public MainPage()
{
    InitializeComponent();

    var shipment = new Shipment();

    foreach (var item in shipment.ShippingOrders)
    {
        ShippingItems.Add(new ShippingItemGroup(item, item.ShippingItems));
    }

    BindingContext = this;

    var rnd = new Random();
    Task.Run(async () =>
    {
        while (true)
        {
            await Task.Delay(1500);
            foreach (var shippingItemGroup in ShippingItems)
            {
                foreach (var shippingItem in shippingItemGroup)
                {
                   shippingItem.BackgroundColor = Color.FromRgb(rnd.Next(256), rnd.Next(256), rnd.Next(256)).ToHex();
                }
            }
       }
    });
}

public ObservableCollection<ShippingItemGroup> ShippingItems { get; private set; } = new ObservableCollection<ShippingItemGroup>();

enter image description here

Related