Reputation: 4498
I have a situation where I want to handle the CollectionChanged event of an ObservableCollection, but only when the collection is changed by the UI. The collection is bound to a DataGrid, and I need to check some things after a user adds or removes something. However, when I populate the collection from code, I don't need to handle the event.
The way I've traditionally handled this is by unhooking the event while I make my changes. But this seems kind of "hackish". Another idea is to set/unset a class Boolean that controls when the event code is executed. But this also seems kind of ugly.
Are there any patterns or best practices for this situation?
Upvotes: 1
Views: 370
Reputation: 203823
This can be handled by creating a wrapper for an ObservableCollection. It will implement the interfaces needed to modify the collection, which can just pass through to the wrapped observable collection, but it will handle setting a boolean when it is calling the base methods, and its handler won't be fired when that boolean is set.
This means that you can create a wrapper, add a handler to the event, and then use that wrapper to manipulate the collection in code without having it fire events, but while still getting events from the UI (assuming you bind the UI to the underlying collection, not the wrapper.
You can add to this by adding some of the other interfaces that ObservableCollection
implements, such as the read only versions. You may also want to expose the underlying collection publicly, if you want to be able to get the "real" one back out.
The advantage here is that while the setting of the boolean is still happening, you're no longer needing to do it all throughout your code; it's all moved to one place.
public class ObservableWrapper<T> :
ICollection<T>, IList<T>, INotifyCollectionChanged
{
private ObservableCollection<T> other;
private bool changing = false;
public ObservableWrapper(ObservableCollection<T> wrapped)
{
other = wrapped;
other.CollectionChanged += (sender, args) =>
{
var handler = CollectionChanged;
if (handler != null && !changing)
handler(sender, args);
};
}
public void Add(T item)
{
changing = true;
other.Add(item);
changing = false;
}
public void Clear()
{
changing = true;
other.Clear();
changing = false;
}
public bool Contains(T item)
{
return other.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
other.CopyTo(array, arrayIndex);
}
public int Count
{
get { return other.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(T item)
{
changing = true;
bool result = other.Remove(item);
changing = false;
return result;
}
public int IndexOf(T item)
{
return IndexOf(item);
}
public void Insert(int index, T item)
{
changing = true;
other.Insert(index, item);
changing = false;
}
public void RemoveAt(int index)
{
changing = true;
other.RemoveAt(index);
changing = false;
}
public T this[int index]
{
get
{
return other[index];
}
set
{
changing = true;
other[index] = value;
changing = false;
}
}
public IEnumerator<T> GetEnumerator()
{
return other.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return other.GetEnumerator();
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
}
Upvotes: 3
Reputation: 8791
Since you seem to have custom requirements I suggest you to have a completely standalone list doing whatever you need instead of "hacking" ObservableCollection.
Let the ObservableCollection be the ObservableCollection and you create a class that implements INotifyCollectionChanged and IList.
You would need to override add, remove,..etc methods but in the end you have some kind of your own smart collection which may be reused and it will be doing what you asking for.
The DataGrid you are talking about needs to know that your custom collection has the IList interface implemented in order to call the add or remove methods. DataGrid doesn't fire CollectionChanged event by itself. It just calls those add, remove, replace..etc methods therefore you need to call CollectionChanged explicity inside those methods which change the collection.
This is the reason why sender in CollectionChanged event always is the instance itself and not some wpf control.
Upvotes: 1
Reputation: 5769
As for many other events, you may check the sender object passed to your event handler in order to see if it was the DataGrid
, a general UIElement
, or your ViewModel; for instance:
yourObservableCollection.CollectionChanged +=
(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs args) =>
{
if (sender is System.Windows.Controls.DataGrid)
{
...
}
}
Upvotes: -2