WPF ListView: Changing ItemsSource does not change ListView
Asked Answered
R

7

23

I am using a ListView control to display some lines of data. There is a background task which receives external updates to the content of the list. The newly received data may contain less, more or the same number of items and also the items itself may have changed.

The ListView.ItemsSource is bound to an OberservableCollection (_itemList) so that changes to _itemList should be visible also in the ListView.

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

In order to avoid refreshing the complete ListView I do a simple comparison of the newly retrieved list with the current _itemList, change items which are not the same and add/remove items if necessary. The collection "newList" contains newly created objects, so replacing an item in _itemList is correctly sending a "Refresh" notification (which I can log by using the event handler OnCollectionChanged of the 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);

My problem is that if many items are changed in this loop, the ListView is NOT refreshing and the data on screen stay as they are...and this I don't understand.

Even a simpler version like this (exchanging ALL elements)

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

is not working properly

Any clue on this?

UPDATE

If I call the following code manually after updating all elements, everything works fine

OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));

But of course this causes the UI to update everything which I still want to avoid.

Reinareinald answered 8/1, 2014 at 12:55 Comment(10)
Did you try a synchronous Dispatcher.Invoke instead of the asynchronous Dispatcher.BeginInvoke?Filthy
I get the same results using Dispatcher.InvokeReinareinald
About how many items are we talking here? And did you try different dispatcher priorities?Filthy
The list has about 100 items and changing e.g. 4 of them works fine, changing 10 has no visual effect. I tried several priorities, up to now no change of behaviourReinareinald
And just to make sure there is nothing wrong in your update code, how does it behave if you simply clear the entire _itemList and then add all items from newList?Filthy
This is working perfectly, but of course this is rather slow and I would like to avoid thatReinareinald
And you are positively sure that changed items are at the proper index positions in the new list, and also that your SameDataAs method is ok?Filthy
I posted a new, simpler version above which still does only work sometimes if several items get replacedReinareinald
And the new items in newList are really references to new instances of class PmemCombItem. Or is there any chance they are referencing the same objects?Filthy
I added the code for creating newList above...Reinareinald
M
44

After a change, you can use the following to refresh the Listview, it's more easy

listView.Items.Refresh();
Misdirection answered 17/6, 2014 at 20:53 Comment(4)
And how to do that using MVVM?Hygrometric
That does not seem to work if you have actually replaced the ItemsSource with a new object. In that case we need to set ItemsSource to null first as Xopher suggests, or rather as Anatolii suggests maybe is better.Whimsey
@MatthisKohli only way I can think of is for View Model to expose an event that the view can subscribe to which just invokes the code above. Maybe not ideal...Monochloride
Isn't this the exact reason TO use an ObservableCollection and WPF? To avoid having to use hardcoded Refresh calls. Since this is an old issue, noting this still has the problem in VS 2022. I just used the Refresh method since it is readable. Noting an answer below, adding in a special event handler and logic when that is literally what the object is supposed to do automatically seems to be a risk to deprecation or a fix to this issue in the future. Explicitly calling Refresh again seems to, while not ideal, futureproof it much better while remaining as simple as possible currently.Farce
L
34

This is what I had to do to get it to work.

MyListView.ItemsSource = null;
MyListView.ItemsSource = MyDataSource;
Lollapalooza answered 4/5, 2014 at 21:20 Comment(0)
L
6

I know that's an old question, but I just stumbled upon this issue. I didn't really want to use the null assignation trick or the refresh for just a field that was updated.

So, after looking at MSDN, I found this article: https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?redirectedfrom=MSDN&view=netframework-4.7.2

To summarize, you just need the item to implement this interface and it will automatically detect that this object can be observed.

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

So, the event will be called everytime someone changes the Status. And, in your case, the listview will add a handler automatically on the PropertyChanged event.

This doesn't really handle the issue in your case (add/remove). But for that, I would suggest that you have a look at BindingList<T> https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.bindinglist-1?view=netframework-4.7.2

Using the same pattern, your listview will be updated properly without using any tricks.

Laconia answered 17/1, 2019 at 3:7 Comment(0)
F
5

You should not reset ItemsSource of ListView each time observable collection changed. Just set proper binding that will do your trick. In xaml:

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

And in code-behind (suggest to use MVVM) property that will be responsible for holding _itemList:

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


UPDATE: There is similar post which most probably will Answer your question: How do I update an ObservableCollection via a worker thread?
Fickle answered 8/1, 2014 at 13:11 Comment(6)
I am setting the ItemsSource property only once during initialization, only the second piece of code is executed when the list gets changedReinareinald
@Reinareinald Yes, but it's not the way it should be used. Set != bind.Fickle
Despite that this does not answer the question (and using a binding or not doesn't matter here), why have a two-way binding on an ItemsSource property?Filthy
@Filthy True. It is not needed.Fickle
Sorry, but even after your edit this does not answer the question. It really doesn't matter if you bind the ItemsSource property to an ObservableCollection or if you set it directly. In both cases changes to the collection are notified by the ObservableCollection. The binding never comes into play again.Filthy
To add another thing: When only a few items are changed, the code works fine and the ListView shows the new items. If in the loop many items get changed, the ListView is not changing at allReinareinald
V
0

I found a way to do it. It is not really that great but it works.

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

this should refresh your ListView (it works for my UAP :) )

Viscera answered 24/2, 2016 at 18:50 Comment(1)
Duplicate of the answer provided by XopherTriste
J
0

An alternative on Xopher's answer.

MyListView.ItemsSource = MyDataSource.ToList();

This refreshes the Listview because it's a other list.

Jointly answered 4/11, 2020 at 17:36 Comment(0)
C
0

Please check this answer: Passing ListView Items to Commands using Prism Library

List view Items needs to notify about changes (done is setter)

public ObservableCollection<Model.Step> Steps 
{
    get { return _steps; }
    set { SetProperty(ref _steps, value); }
} 

and UpdateSourceTrigger need to be set in xaml

<Image Source="{Binding ImageData, UpdateSourceTrigger=PropertyChanged}" />
Charlyncharm answered 20/7, 2021 at 13:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.