Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ListView not updated correctly with ObservableCollection

I'm currently using an observable collection to store my data objects for a ListView. Adding new objects to the collection works just fine, and the listView updates properly. However when I try to change one of the properties of an object in the collection the listView will not update properly. For example, I have an observable collection DataCollection. I try

_DataCollections.ElementAt(count).Status = "Active";

I perform this change before a long operation due to a button press. The listView will not reflect the change. So I addmyListView.Items.Refresh();. This works, however the listView does not get refreshed till button_click method is complete, which is no good by then. For example:

   button1_Click(...)
    {
      _DataCollections.ElementAt(count).Status = "Active";
      myListView.Items.Refresh();
      ExecuteLongOperation();
      _DataCollections.ElementAt(count).Status = "Finished";
      myListView.Items.Refresh();
    }

The status will never goto "Active", it will go straight to "Finished" after the method completes. I also tried using a dispatcher like this:

button1_Click(...)
    {
      this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background,
            (NoArgDelegate)delegate { _DataCollection.ElementAt(count).Status =  "Active"; myListView.Items.Refresh(); });

      ExecuteLongOperation();
     this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background,
            (NoArgDelegate)delegate { _DataCollection.ElementAt(count).Status =  "Finished"; myListView.Items.Refresh(); });

    }

However, that does not seem to work correctly either. Any tips or ideas would be appreciated.

like image 265
user793491 Avatar asked Jun 30 '11 23:06

user793491


3 Answers

You have to use proper data binding techniques, and then this will work automagically.

Required...

  1. Implement INotifyPropertyChanged on your class inside the ObservableCollection (and make sure you're triggering the event when you are setting properties on that class)
  2. On your ListView's ItemTemplate, be sure you're using Binding to the properties

If you do those two things, there's no need for a "Refresh" call or anything else. Setting a property that triggers INotifyPropertyChanged will cause the Binding of the ItemTemplate to update.

Implementing INotifyPropertyChanged on your class inside the ObservableCollection... (look up the BindableBase class if you don't know about it already)

public class ToDoItem : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private string _name;
    public string Name
    {
        get { return _name; }
        set { SetProperty(ref _name, value); }
    }

    private DateTime _date;
    public DateTime Date
    {
        get { return _date; }
        set { SetProperty(ref _date, value); }
    }

    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
    {
        if (object.Equals(storage, value)) return false;

        storage = value;
        this.OnPropertyChanged(propertyName);
        return true;
    }

    protected void OnPropertyChanged(string propertyName)
    {
        var eventHandler = this.PropertyChanged;
        if (eventHandler != null)
        {
            eventHandler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Your ListView

<ListView
    x:Name="listView">

    <ListView.ItemTemplate>
        <DataTemplate>

            <StackPanel>

                <TextBlock
                    Text="{Binding Name}"/>

                <TextBlock
                    Text="{Binding Date}"/>

            </StackPanel>

        </DataTemplate>
    </ListView.ItemTemplate>

</ListView>

Your ObservableCollection...

private ObservableCollection<ToDoItem> _toDoItems = new ObservableCollection<ToDoItem>();

// Assign the collection to the ListView
listView.ItemsSource = _toDoItems;

Adding things to the collection works...

_toDoItems.Add(new ToDoItem()
{
    Name = "Item " + (_toDoItems.Count + 1),
    Date = DateTime.Now
});

And updating, what you were asking for, works...

ToDoItem item = _toDoItems[randomIndex];

item.Name = "Updated " + item.Name;
item.Date = DateTime.Now;

No calls to "Refresh" or anything else needed. The item itself updates, without the list changing.

Before updating Item 4...

Before updating Item 4

After updating Item 4...

After updating Item 4

Full code sample available here: CODE SAMPLE

like image 157
Andrew Leader Avatar answered Dec 26 '22 11:12

Andrew Leader


To solve this I created a class called VeryObservableCollection. For each object you add, it hooks the object's NotifyPropertyChanged event to a handler that triggers a CollectionChanged event. For each object removed, it removes the handler. Very simple and will give you exactly what you want. Partial code:

public class VeryObservableCollection<T> : ObservableCollection<T>

/// <summary>
/// Override for setting item
/// </summary>
/// <param name="index">Index</param>
/// <param name="item">Item</param>
protected override void SetItem(int index, T item)
{
    try
    {
        INotifyPropertyChanged propOld = Items[index] as INotifyPropertyChanged;
        if (propOld != null)
            propOld.PropertyChanged -= new PropertyChangedEventHandler(Affecting_PropertyChanged);
    }
    catch (Exception ex)
    {
        Exception ex2 = ex.InnerException;
    }
    INotifyPropertyChanged propNew = item as INotifyPropertyChanged;
    if (propNew != null)
        propNew.PropertyChanged += new PropertyChangedEventHandler(Affecting_PropertyChanged);

    base.SetItem(index, item);
}
like image 36
Ed Bayiates Avatar answered Dec 26 '22 10:12

Ed Bayiates


You have run into the classic problem with ObservableCollection. it only notifies when an item is added or removed. it does NOT notify when a property of an item in the collection changes. if you want to be notified of such changes you are going to have to make your own custom collection and add/remove the property changed events on the individual objects manually. sorry, dude.

like image 29
Muad'Dib Avatar answered Dec 26 '22 10:12

Muad'Dib