It appears that filtering an ObservableCollection
with CollectionViewSource
is not possible in WinRT:
I can filter using LINQ, but how do I get the UI to update if changes that affect the filtered data are made?
I ended up writing my own class to achieve the desired effect:
public class ObservableCollectionView<T> : ObservableCollection<T>
{
private ObservableCollection<T> _view;
private Predicate<T> _filter;
public ObservableCollectionView(IComparer<T> comparer)
: base(comparer)
{
}
public ObservableCollectionView(IComparer<T> comparer, IEnumerable<T> collection)
: base(comparer, collection)
{
}
public ObservableCollectionView(IComparer<T> comparer, IEnumerable<T> collection, Predicate<T> filter)
: base(comparer, collection == null ? new T[] { } : collection)
{
if (filter != null)
{
_filter = filter;
if (collection == null)
_view = new ObservableCollection<T>(comparer);
else
_view = new ObservableCollection<T>(comparer, collection);
}
}
public ObservableCollection<T> View
{
get
{
return (_filter == null ? this : _view);
}
}
public Predicate<T> Filter
{
get
{
return _filter;
}
set
{
if (value == null)
{
_filter = null;
_view = new ObservableCollection<T>(Comparer);
}
else
{
_filter = value;
Fill();
}
}
}
private void Fill()
{
_view = new ObservableCollection<T>(Comparer);
foreach (T item in this)
{
if (Filter(item))
View.Add(item);
}
}
private int this[T item]
{
get
{
int foundIndex = -1;
for (int index = 0; index < View.Count; index++)
{
if (View[index].Equals(item))
{
foundIndex = index;
break;
}
}
return foundIndex;
}
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
if (_filter != null)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (T item in e.NewItems)
if (Filter(item))
View.Add(item);
break;
case NotifyCollectionChangedAction.Move:
break;
case NotifyCollectionChangedAction.Remove:
foreach (T item in e.OldItems)
if (Filter(item))
View.Remove(item);
break;
case NotifyCollectionChangedAction.Replace:
for (int index = 0; index < e.OldItems.Count; index++)
{
T item = (T)e.OldItems[index];
if (Filter(item))
{
int foundIndex = this[item];
if (foundIndex != -1)
View[foundIndex] = (T)e.NewItems[index];
}
}
break;
case NotifyCollectionChangedAction.Reset:
Fill();
break;
}
}
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (_filter != null)
{
// TODO: Implement code for property changes
}
}
}
Not yet perfect. So improvements/suggestions welcomed.
You can now bind this object, directly to a control using the View property.
You need to make sure the filtering changes are observable, so you can set the source of the CollectionViewSource
to an ObservableCollection
and make the changes on that collection or assign a new Source
of the CVS to a new, filtered collection.
来源:https://stackoverflow.com/questions/16244596/metro-app-collectionviewsource-observablecollection-filter