首页 > 解决方案 > 使用 Observable 进行 CollectionView 分组

问题描述

按照这个示例为 CollectionView 创建分组,我注意到没有一个属性是 INotifyPropertyChanged,基类也不是 ObservableCollection。

而后者很容易通过将 List 更改为 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); }
}

目前尚不清楚如何使 Name 或任何其他属性(例如 SomeOtherPropertyIWantToChangeAtRuntime),我想与组关联为 INotifyPropertyChanged。通过将接口添加到 base 将其视为普通类会导致此警告:

基本接口“INotifyPropertyChanged”是多余的,因为 AnimalGroup 继承了“ObservableCollection”

然而,setter 没有任何东西可以调用,例如 SetProperty(ref _name, Value) 并且现有的 PropertyChanged 对象仅用于监视组的集合更改。它不是可调用的,只是可处理的。

如果我忽略警告并无论如何实施 INotifyPropertyChanged(并将我的事件命名为 PropChanged 以避免与 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;

并让我的 ViewModel 管理 SomeOtherPropertyIWantToChangeAtRuntime 的值,绑定<Label>永远不会看到任何更改。

<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>
...

视图模型:

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);
}

结果是标签保持“嘿”(这是默认值)并且永远不会改变,即使我可以看到上面的命令触发并且代码找到了组并设置了文本。

标签: xamarin.formsgroupingobservablecollectioninotifypropertychangedcollectionview

解决方案


同意杰森,ObservableCollection继承了INotifyPropertyChanged接口,所以你会得到警告

基本接口“INotifyPropertyChanged”是多余的,因为 AnimalGroup 继承了“ObservableCollection”

请参阅以下有关ObservableCollection<T>. 在此处输入图像描述

如果你想像这个 GIF 一样在运行时更改项目。

在此处输入图像描述

根据您的代码。Animal我在类中添加了两个属性。为了实现在运行时更改属性的文本,我们可以INotifyPropertyChangedAnimal类中实现。这是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));
        }
    }
       

为了测试点击命令,我实现了MyAnimalViewModel.cs类似下面的代码。

  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";
            });
         }
    }

我注意到您想要实现 CollectionView 的组。这是我编辑的布局。

  <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>

这是布局背景代码。

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

推荐阅读