Xamarin Grouped ListView - 除非我滚动,否则编辑项目不会更新 UI

Xamarin Grouped ListView - Editing Item doesn't update UI unless I scroll

所以我正在尝试在 Xamarin 中构建自定义分组多选,但我遇到了 UI 未正确更新的问题。

这是我的视图代码:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="testApp.Views.TestPage"
             xmlns:viewmodels="clr-namespace:testApp.ViewModels">
    <ContentPage.BindingContext>
        <viewmodels:TestViewModel/>
    </ContentPage.BindingContext>
    <ContentPage.Content>
        <ListView ItemsSource="{Binding ItemsGrouped}"
                  CachingStrategy="RecycleElement"
                  GroupDisplayBinding="{Binding GroupTitle}"
                  IsGroupingEnabled="true"
                  GroupShortNameBinding="{Binding GroupTitle}"
                  Margin="20"
                  SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
            <ListView.GroupHeaderTemplate>
                <DataTemplate>
                    <ViewCell Height="25">
                        <Label Text="{Binding GroupTitle}" TextColor="Black" VerticalOptions="Center"/>
                    </ViewCell>
                </DataTemplate>
            </ListView.GroupHeaderTemplate>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Grid Margin="20,0,0,0">
                            <!--<Grid.GestureRecognizers>
                                <TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:TestViewModel}}, Path=OnItemTappedCommand}" CommandParameter="{Binding .}" />
                            </Grid.GestureRecognizers>-->
                            <Grid.RowDefinitions>
                                <RowDefinition Height="*" />
                                <RowDefinition Height="*" />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition Width="*" />
                            </Grid.ColumnDefinitions>
                            <Label Grid.Row="0" Grid.Column="0" Text="{Binding Heading}" FontAttributes="Bold" />
                            <Label Grid.Row="1" Grid.Column="0" Text="{Binding SubText}" />
                            <Image Grid.Row="0" Grid.RowSpan="2" Grid.Column="1" Source="Checkmark" HorizontalOptions="End" IsVisible="{Binding Selected}" />
                        </Grid>
                    </ViewCell>

                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

    </ContentPage.Content>
</ContentPage>

这是我的视图模型的代码:

using testApp.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using Xamarin.Forms;

namespace testApp.ViewModels
{
    public class TestViewModel : BaseViewModel
    {
        private ObservableCollection<MultiSelectItem> items;
        public ObservableCollection<MultiSelectItem> Items
        {
            get { return items; }
            set { items = value; OnPropertyChanged(nameof(Items)); }
        }
        private ObservableCollection<MultiSelectItemGrouping<string, MultiSelectItem>> itemsGrouped;
        public ObservableCollection<MultiSelectItemGrouping<string, MultiSelectItem>> ItemsGrouped
        {
            get { return itemsGrouped; }
            set { itemsGrouped = value; OnPropertyChanged(nameof(ItemsGrouped)); }
        }

        private MultiSelectItem selectedItem;
        public MultiSelectItem SelectedItem
        {
            get { return selectedItem; }
            set
            {
                MultiSelectItem item = value;

                if (item != null)
                {
                    bool found = false;

                    foreach (var result in Items)
                    {
                        if (result.Equals(item))
                        {
                            found = true;
                            item.Selected = !item.Selected;
                            item.SubText = "I was selected";
                            break;
                        }
                    }

                    if (found)
                    {
                        GroupData();
                    }

                    value = null;
                }
                selectedItem = value;
                OnPropertyChanged(nameof(SelectedItem));
            }
        }

        public Command OnItemTappedCommand { get; }

        public TestViewModel()
        {
            Items = GetData();

            GroupData();

            OnItemTappedCommand = new Command(OnItemTapped);
        }

        public void GroupData()
        {
            IEnumerable<MultiSelectItemGrouping<string, MultiSelectItem>> groupedItems = Items.GroupBy(x => x.GroupName).Select(group => new MultiSelectItemGrouping<string, MultiSelectItem>(group.Key, group));
            ItemsGrouped = new ObservableCollection<MultiSelectItemGrouping<string, MultiSelectItem>>(groupedItems);
        }

        private void OnItemTapped(object parameter)
        {
            MultiSelectItem item = (MultiSelectItem)parameter;

            item.Selected = !item.Selected;
        }

        private ObservableCollection<MultiSelectItem> GetData()
        {
            return new ObservableCollection<MultiSelectItem>()
            {
                new MultiSelectItem()
                {
                    GroupName = "TestCompany",
                    Heading = "TestCompany1",
                    SubText = "TestCompanySub1"
                },
                new MultiSelectItem()
                {
                    GroupName = "Bruh",
                    Heading = "Bruh2",
                    SubText = "BruhSub2",
                    Selected = true
                },
                new MultiSelectItem()
                {
                    GroupName = "TestCompany",
                    Heading = "TestCompany2",
                    SubText = "TestCompanySub2"
                },
                new MultiSelectItem()
                {
                    GroupName = "Blergh",
                    Heading = "Blergh2",
                    SubText = "BlerghSub2"
                },
                new MultiSelectItem()
                {
                    GroupName = "TestCompany",
                    Heading = "TestCompany3",
                    SubText = "TestCompanySub3",
                    Selected = true
                },
                new MultiSelectItem()
                {
                    GroupName = "Blergh",
                    Heading = "Blergh1",
                    SubText = "BlerghSub1"
                },
                new MultiSelectItem()
                {
                    GroupName = "Cat",
                    Heading = "I Like Cats",
                    SubText = "Yup",
                    Selected = true
                },
                new MultiSelectItem()
                {
                    GroupName = "Bruh",
                    Heading = "Bruh1",
                    SubText = "BruhSub1"
                },
                new MultiSelectItem()
                {
                    GroupName = "Bruh",
                    Heading = "Bruh3",
                    SubText = "BruhSub3"
                },
                new MultiSelectItem()
                {
                    GroupName = "Bruh",
                    Heading = "Bruh4",
                    SubText = "BruhSub4"
                },
                new MultiSelectItem()
                {
                    GroupName = "Test",
                    Heading = "Test1",
                    SubText = "TestSub1",
                    Selected = true
                },
                new MultiSelectItem()
                {
                    GroupName = "Test",
                    Heading = "Test2",
                    SubText = "TestSub2"
                },
                new MultiSelectItem()
                {
                    GroupName = "Test",
                    Heading = "Test3",
                    SubText = "TestSub3"
                },
                new MultiSelectItem()
                {
                    GroupName = "Test",
                    Heading = "Test4",
                    SubText = "TestSub4",
                    Selected = true
                },
                new MultiSelectItem()
                {
                    GroupName = "Test",
                    Heading = "Test5",
                    SubText = "TestSub5"
                },
                new MultiSelectItem()
                {
                    GroupName = "Test",
                    Heading = "Test6",
                    SubText = "TestSub6"
                },
                new MultiSelectItem()
                {
                    GroupName = "Test",
                    Heading = "Test7",
                    SubText = "TestSub7"
                },
                new MultiSelectItem()
                {
                    GroupName = "Test",
                    Heading = "Test8",
                    SubText = "TestSub8"
                },
                new MultiSelectItem()
                {
                    GroupName = "Test",
                    Heading = "Test9",
                    SubText = "TestSub9"
                },
            };
        }
    }
}

TestViewModel 继承自的我的 BaseViewModel 实现了 INotifyPropertyChanged,它在所有其他屏幕上都能正常工作。当我单步执行代码时,我可以看到 SubText 和 Selected 属性发生变化,但 UI 永远不会更新。但是,如果我滚动出视图,然后返回视图,它会更新。

因为我使用的是 MVVM,所以我不能完全刷新 ListView,但这看起来很奇怪。

我们将不胜感激任何帮助。

编辑:这里要求的是 MultiSelectItem 的代码 class

using System;
using System.Collections.Generic;
using System.Text;
namespace testApp.Models
{
    public class MultiSelectItem
    {
        public string GroupName { get; set; }
        public string Heading { get; set; }
        public string SubText { get; set; }
        public int ID { get; set; }
        public bool Selected { get; set; }
    }
}

正如 Jason 所说,问题出在 MultiSelectItem class 上。我没有意识到它也需要实现 INotifyPropertyChanged,但它确实需要。将其添加到修复的所有内容中。