WPF ListView: изменение ItemsSource не меняет ListView

Я использую элемент управления ListView для отображения некоторых строк данных. Существует фоновая задача, которая получает внешние обновления для содержимого списка. Недавно полученные данные могут содержать меньше, больше или одинаковое количество элементов, а также сами элементы могут быть изменены.

ListView.ItemsSource привязан к OberservableCollection (_itemList), так что изменения в _itemList должны быть видны также в ListView.

_itemList = new ObservableCollection<PmemCombItem>();
_itemList.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
L_PmemCombList.ItemsSource = _itemList;

Чтобы избежать обновления полного списка ListView, я делаю простое сравнение недавно полученного списка с текущим _itemList, изменяя элементы, которые не являются одинаковыми, и при необходимости добавьте/удалите элементы. Коллекция "newList" содержит вновь созданные объекты, поэтому замена элемента в _itemList корректно отправляет уведомление "Обновить" (которое я могу зарегистрировать с помощью обработчика событий OnCollectionChanged ObservableCollection`)

Action action = () =>
{
    for (int i = 0; i < newList.Count; i++)
    {
        // item exists in old list -> replace if changed
        if (i < _itemList.Count)
        {
            if (!_itemList[i].SameDataAs(newList[i]))
                _itemList[i] = newList[i];
        }
        // new list contains more items -> add items
        else
            _itemList.Add(newList[i]);
     }
     // new list contains less items -> remove items
     for (int i = _itemList.Count - 1; i >= newList.Count; i--)
         _itemList.RemoveAt(i);
 };
 Dispatcher.BeginInvoke(DispatcherPriority.Background, action);

Моя проблема в том, что если в этом цикле меняются многие элементы, ListView НЕ обновляется, а данные на экране остаются такими, какие они есть... и этого я не понимаю.

Даже более простая версия, подобная этой (замена ВСЕХ элементов)

List<PmemCombItem> newList = new List<PmemCombItem>();
foreach (PmemViewItem comb in combList)
    newList.Add(new PmemCombItem(comb));

if (_itemList.Count == newList.Count)
    for (int i = 0; i < newList.Count; i++)
        _itemList[i] = newList[i];
else
{
    _itemList.Clear();
    foreach (PmemCombItem item in newList)
        _itemList.Add(item);
}

работает неправильно

Какой-нибудь ключ к этому?

UPDATE

Если после обновления всех элементов я вызываю следующий код вручную, все работает отлично

OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));

Но, конечно, это заставляет пользовательский интерфейс обновлять все, чего я все еще хочу избежать.

Ответ 1

Это то, что я должен был сделать, чтобы заставить его работать.

MyListView.ItemsSource = null;
MyListView.ItemsSource = MyDataSource;

Ответ 2

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

listView.Items.Refresh();

Ответ 3

Вы не должны reset ItemsSource of ListView каждый раз, когда изменяется наблюдаемая коллекция. Просто установите правильное связывание, которое сделает ваш трюк. В xaml:

<ListView ItemsSource='{Binding ItemsCollection}'
 ...
</ListView>

И в коде (подскажите использовать MVVM) свойство, которое будет нести ответственность за сохранение _itemList:

public ObservableCollection<PmemCombItem> ItemsCollection
{
   get 
   {
      if (_itemList == null)
      {
          _itemList = new ObservableCollection<PmemCombItem>();
      }
      return _itemList;
   }
}


UPDATE: Существует аналогичная запись, которая, скорее всего, ответит на ваш вопрос: Как обновить ObservableCollection с помощью рабочего потока?

Ответ 4

Я нашел способ сделать это. Это не очень здорово, но оно работает.

YourList.ItemsSource = null;
// Update the List containing your elements (lets call it x)
YourList.ItemsSource = x;

это должно обновить ваш ListView (он работает для моего UAP:))

Ответ 5

Я знаю, что старый вопрос, но я просто наткнулся на этот вопрос. Я действительно не хотел использовать трюк с нулевым присваиванием или обновление только для поля, которое было обновлено.

Итак, после просмотра MSDN я обнаружил эту статью: https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?redirectedfrom=MSDN&view=netframework-4.7.2

Подводя итог, вам просто нужен элемент для реализации этого интерфейса, и он автоматически обнаружит, что этот объект можно наблюдать.

public class MyItem : INotifyPropertyChanged
{
    private string status;

    public string Status
    {
        get => status;
        set
        {
            OnPropertyChanged(nameof(Status));
            status = value;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Таким образом, событие будет вызываться каждый раз, когда кто-то меняет Status. И, в вашем случае, просмотр списка автоматически добавит обработчик в событие PropertyChanged.

Это действительно не решает проблему в вашем случае (добавить/удалить). Но для этого я бы посоветовал вам взглянуть на BindingList<T> https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.bindinglist-1?view=netframework-4.7.2.

Используя тот же шаблон, ваш список будет обновляться должным образом, без каких-либо хитростей.