Обновить отображение одного элемента в ObservableCollection ListView

У меня есть ListView, который привязан к ObservableCollection.

Есть ли способ обновить одну ячейку при изменении свойства элемента SomeModel без перезагрузки ListView путем изменения ObservableCollection?

(Вопрос скопирован с https://forums.xamarin.com/discussion/40084/update-item-properties-in-a-listviews-observablecollection, как и мой ответ там.)

2 ответа

Решение

Как я вижу, вы пытаетесь использовать MVVM в качестве шаблона для своего приложения Xamarin.Forms. Вы уже используетеObservableCollectionдля отображения списка данных. Когда новый элемент добавляется или удаляется из коллекции, пользовательский интерфейс обновляется соответствующим образом, потому чтоObserverbleCollection реализует INotifyCollectionChanged.

С помощью этого вопроса вы хотите достичь следующего поведения, когда вы хотите изменить конкретное значение для элемента в коллекции и обновить пользовательский интерфейс, лучший и самый простой способ добиться этого - реализовать INotifyPropertyChanged за модель предмета из вашей коллекции.

Беллоу, у меня есть простой демонстрационный пример того, как этого добиться, ваш ответ работает, как я вижу, но я уверен, что вам было бы лучше использовать этот пример.

У меня просто Button с командой и ListView который содержит данные моей коллекции.

Вот моя страничка, SimpleMvvmExamplePage.xaml:

<StackLayout>

    <Button Text="Set status" 
            Command="{Binding SetStatusCommand}" 
            Margin="6"/>

    <ListView ItemsSource="{Binding Cars}"
              HasUnevenRows="True">

        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>

                    <StackLayout Orientation="Vertical" 
                                 Margin="8">

                        <Label Text="{Binding Name}" 
                               FontAttributes="Bold" />

                        <StackLayout Orientation="Horizontal">

                            <Label Text="Seen?" 
                                   VerticalOptions="Center"/>

                            <CheckBox IsChecked="{Binding Seen}"
                                      Margin="8,0,0,0" 
                                      VerticalOptions="Center" 
                                      IsEnabled="False" />
                        </StackLayout>

                     </StackLayout>

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

 </StackLayout>

Основная идея этой демонстрации - изменить значение свойства Seen и установите значение для CheckBox когда пользователь нажимает эту кнопку над ListView.

Это мое Car.cs учебный класс.

public class Car : INotifyPropertyChanged
{
    private string name;
    public string Name
    {
        get { return name; }
        set 
        { 
            name = value;
            OnPropertyChanged();
        }
    }

    private bool seen;
    public bool Seen
    {
        get { return seen; }
        set 
        { 
            seen = value;
            OnPropertyChanged();
        }
    }

    // Make base class for this logic, something like BindableBase
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

В полном демонстрационном примере, который есть на моем Github, я использую свой BindableBase класс, где я занимаюсь повышением INotifyPropertyChanged когда какое-либо значение свойства изменяется с помощью этого метода SetProperty в установщике свойств.

Вы можете найти реализацию здесь: https://github.com/almirvuk/Theatrum/tree/master/Theatrum.Mobile/Theatrum.Mobile

Последнее, что нужно показать, это мой ViewModel для этой страницы и внутри ViewModel, Я изменю значение Seen собственность True для элементов в коллекции, когда пользователь нажимает на Button выше ListView. Вот мойSimpleMvvmExamplePageViewModel.cs

public class SimpleMvvmExamplePageViewModel
{
    public ObservableCollection<Car> Cars { get; set; }

    public ICommand SetStatusCommand { get; private set; }

    public SimpleMvvmExamplePageViewModel()
    {
        // Set simple dummy data for our ObservableCollection of Cars
        Cars = new ObservableCollection<Car>()
        {
            new Car()
            {
                Name = "Audi R8",
                Seen = false
            },

            new Car()
            {
                Name = "BMW M5",
                Seen = false
            },

            new Car()
            {
                Name = "Ferrari 430 Scuderia",
                Seen = false
            },

            new Car()
            {
                Name = "Lamborghini Veneno",
                Seen = false
            },

            new Car()
            {
                Name = "Mercedes-AMG GT R",
                Seen = false
            }
        };

        SetStatusCommand = new Command(SetStatus);
    }

    private void SetStatus()
    {
        Car selectedCar = Cars.Where(c => c.Seen == false)
            .FirstOrDefault();

        if (selectedCar != null)
        {
            // Change the value and update UI automatically
            selectedCar.Seen = true;
        }
    }
}

Этот код поможет нам добиться такого поведения: когда пользователь нажимает на Button мы изменим значение свойства элемента из коллекции, и UI будет обновлен, значение флажка будет отмечено.

Окончательный результат этой демонстрации можно увидеть на этой гифке ниже.

PS Я мог бы совместить это с ItemTapped событие от ListView но я хотел сделать это очень простым, чтобы этот пример был таким.

Надеюсь, это было полезно для вас, желаю удачи в программировании!

Любой пользовательский интерфейс, связанный с элементом модели, будет обновлен при замене элемента на себя в Observable Collection.

Детали:

В ViewModel данное свойство:

public ObservableCollection<Item> Items { get; set; } = new ObservableCollection<Item>();

где Itemэто ваш модельный класс.
После добавления некоторых элементов (не показаны), предположим, вы хотите, чтобы элемент "item" обновился:

public void RefreshMe(Item item)
{
    // Replace the item with itself.
    Items[Items.IndexOf(item)] = item;
}

ПРИМЕЧАНИЕ. В приведенном выше коде предполагается, что "item" находится в "Items". Если это не известно, проверьте, чтоIndexOf возвращается >= 0 перед выполнением замены.

В моем случае у меня был DataTemplateSelectorв коллекции, и элемент был изменен таким образом, что потребовался другой шаблон. (В частности, щелчок по элементу переключал его между свернутым представлением и развернутым / подробным представлением, поскольку TemplateSelector считывал свойство IsExpanded из элемента модели.)

ПРИМЕЧАНИЕ: протестировано с CollectionView, но AFAIK также будет работать со старыми ListViewучебный класс.
Проверено на iOS и Android.

Техническое примечание:
эта замена предмета предположительно вызывает Replace NotifyCollectionChangedEvent, с newItems а также oldItems оба содержат только item.

Другие вопросы по тегам