0
votes

Following this example to create a grouping for CollectionView, I notice that none of the properties are INotifyPropertyChanged, nor is the base class an ObservableCollection.

While the latter is easy to fix by changing List to ObservableCollection:

public class AnimalGroup : ObservableCollection<Animal>
{
    public string Name { get; private set; }

    public AnimalGroup(string name, ObservableCollection<Animal> animals) : base(animals)
    {
       Name = name;
    }

    private string _someOtherPropertyIWantToChangeAtRuntime = "hey";
    public string SomeOtherPropertyIWantToChangeAtRuntime { get => _someOtherPropertyIWantToChangeAtRuntime, set => SetProperty(ref _someOtherPropertyIWantToChangeAtRuntime, value); }
}

It isn't clear how to make Name, or any other property (e.g. SomeOtherPropertyIWantToChangeAtRuntime), I want to associate with the group as an INotifyPropertyChanged. Treating it is as a normal class by adding the interface to base causes this warning:

Base interface 'INotifyPropertyChanged' is redundant because AnimalGroup inherits 'ObservableCollection'

Yet, there is nothing for the setter to call, such as SetProperty(ref _name, Value) and the existing PropertyChanged object is just for monitoring a group's collection changes. It isn't invokable, just handleable.

If I ignore the warning and implement INotifyPropertyChanged anyway (and name my event PropChanged to avoid colliding with ObservableCollection.PropertyChanged),

protected bool SetProperty<T>(ref T backingStore, T value, [CallerMemberName]string propertyName = "", Action onChanged = null)
{
    if (EqualityComparer<T>.Default.Equals(backingStore, value))
        return false;

    backingStore = value;
        
    onChanged?.Invoke();
        
    PropChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    return true;
}

public event PropertyChangedEventHandler PropChanged;

and let my ViewModel manage the value of SomeOtherPropertyIWantToChangeAtRuntime, the bound <Label> never sees any changes.

<CollectionView ItemsSource="{Binding AnimalGroups}" HorizontalOptions="FillAndExpand">

    <CollectionView.ItemsLayout>
        <LinearItemsLayout Orientation="Vertical"/>
    </CollectionView.ItemsLayout>

    <CollectionView.ItemTemplate>
        <DataTemplate>

            <StackLayout>
                <StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand">
                    <Label 
                        Text="{Binding Name}" 
                        HorizontalOptions="Start" 
                        FontSize="24.44" 
                        TextColor="Black" 
                        FontAttributes="Bold" 
                        Margin="0,0,0,10"/>

                    <Label 
                        Text="{Binding SomeOtherPropertyIWantToChangeAtRuntime}" FontSize="15" 
                        TextColor="Black" 
                        Margin="0,0,0,0">
                        <Label.GestureRecognizers>
                            <TapGestureRecognizer Command="{Binding BindingContext.FindGroupAndChangeTextCommand, Source{x:Reference thisPageName}" CommandParameter="{Binding Name}"/>
                        </Label.GestureRecognizers>
                    </Label>
...

ViewModel:

public ObservableCollection<AnimalGroup> AnimalGroups {get; private set;}

public ICommand FindGroupAndChangeTextCommand {get; private set;}
public void FindGroupAndChangeText(string name)
{
    var group = AnimalGroups.FirstOrDefault(t => t.Name == name);
    if (group != null)
        group.SomeOtherPropertyIWantToChangeAtRuntime = DateTime.Now.ToString();
}

ViewModel()
{
    AnimalGroups = LoadData(); // not shown
    FindGroupAndChangeTextCommand = new Command(FindGroupAndChangeText);
}

The result is that the label remains "hey" (which is the default value) and never changes even though I can see that the above command fires and the code finds the group and sets the text.

1
ObservableCollection has an OnPropertyChanged methodJason
I don't think you understand my problem. The ObservableCollection does have PropertyChanged but it only fires when items are added and removed. This has nothing to do with what I need.John Mc
I do understand, and you should be able to call OnPropertyChanged in your property settersJason
Now I get it. The setter works like this: set {_propname= value; OnPropertyChanged(newPropertyChangedEventArgs(nameof(Propname)));}John Mc

1 Answers

0
votes

Agree with Jason, ObservableCollection has inherited INotifyPropertyChanged interface , So you will get the warning

Base interface 'INotifyPropertyChanged' is redundant because AnimalGroup inherits 'ObservableCollection'

And please see following screenshot about ObservableCollection<T>. enter image description here

If you want to change the item at the runtime like this GIF.

enter image description here

Based on your code. I add two properties in the Animal class. For achieve the change the text of properties at the runtime, we can achieve the INotifyPropertyChanged in Animal class. Here is AnimalGroup.cs

    public class AnimalGroup : ObservableCollection<Animal>
    {
        public string Name { get; private set; }

        public AnimalGroup(string name, ObservableCollection<Animal> animals) : base(animals)
        {
            Name = name;
        }

       
    }

    public class Animal : INotifyPropertyChanged
    {

        string animalName;
        public string AnimalName
        {
            set
            {
                if (animalName != value)
                {
                    animalName = value;
                    OnPropertyChanged("AnimalName");

                }
            }
            get
            {
                return animalName;
            }
        }

        string animalArea;
        public string AnimalArea
        {
            set
            {
                if (animalArea != value)
                {
                    animalArea = value;
                    OnPropertyChanged("AnimalArea");

                }
            }
            get
            {
                return animalArea;
            }
        }


        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
       

For testing click the command, I achieve the MyAnimalViewModel.cs like following code.

  public class MyAnimalViewModel
    {
        public ObservableCollection<AnimalGroup> AnimalGroups { get; private set; } = new ObservableCollection<AnimalGroup>();

        public ICommand FindGroupAndChangeTextCommand { protected set; get; }
        public MyAnimalViewModel()
        {
            ObservableCollection<Animal> ts = new ObservableCollection<Animal>();
            ts.Add(new Animal() { AnimalArea = "Asia", AnimalName = "cat" });
            ts.Add(new Animal() { AnimalArea = "Asia", AnimalName = "dog" });

            ObservableCollection<Animal> ts2 = new ObservableCollection<Animal>();
            ts2.Add(new Animal() { AnimalArea = "Eourp", AnimalName = "keep" });
            ts2.Add(new Animal() { AnimalArea = "Eourp", AnimalName = "gggg" });
            AnimalGroups.Add(new AnimalGroup("Animal1", ts));
            AnimalGroups.Add(new AnimalGroup("Animal2", ts2));


            FindGroupAndChangeTextCommand = new Command<Animal>((key) =>
            {
                key.AnimalName = "testggggg";
            });
         }
    }

I notice you want to achieve the group for CollectionView. Here is my edited layout.

  <ContentPage.Content>

        <CollectionView x:Name="MyCollectionView" ItemsSource="{Binding AnimalGroups}" IsGrouped="True" HorizontalOptions="FillAndExpand">

                <CollectionView.ItemsLayout>
                    <LinearItemsLayout Orientation="Vertical"/>
                </CollectionView.ItemsLayout>
            <CollectionView.GroupHeaderTemplate>
                <DataTemplate>
                    <Label Text="{Binding Name}"
                           BackgroundColor="LightGray"
                           FontSize="Large"
                           FontAttributes="Bold" >
                         
                    </Label>
                </DataTemplate>
            </CollectionView.GroupHeaderTemplate>
            <CollectionView.ItemTemplate>
                    <DataTemplate>

                        <StackLayout>
                            <StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand">
                            <Label 
                        Text="{Binding AnimalArea}" 
                        HorizontalOptions="Start" 
                        FontSize="24.44" 
                        TextColor="Black" 
                        FontAttributes="Bold" 
                        Margin="0,0,0,10"/>

                            <Label 
                                Text="{Binding AnimalName}" FontSize="15" 
                                TextColor="Black" 
                                 Margin="0,0,0,0">
                                  <Label.GestureRecognizers>
                                    <TapGestureRecognizer  NumberOfTapsRequired="1" 
                                                           Command="{ Binding BindingContext.FindGroupAndChangeTextCommand, Source={x:Reference Name=MyCollectionView} }"  CommandParameter="{Binding .}" 
                                                           />
                                  </Label.GestureRecognizers>
                                </Label>
                            </StackLayout>
                        </StackLayout>
                    </DataTemplate>
                </CollectionView.ItemTemplate>
            </CollectionView>
    </ContentPage.Content>

Here is layout background code.

 public partial class Page2 : ContentPage
    {
        public Page2()
        {
            InitializeComponent();
            this.BindingContext = new MyAnimalViewModel();
        }
}