Обновить отображение одного элемента в 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
.