Reputation: 3529
I"m trying to figure out if there is a way to do the following in C# 4.0:
I have a ObservableCollection that contains a large number of custom classes - lets call it "MainCollection". Much of my code updates the values of these classes in MainCollection on an ongoinging basis, which all works fine.
I now need to create a 'collection' [or something to use in a WPF DataGrid DataContext binding] that is simply that groups the classes in MainCollection on a single parameter in the underlying class.
Is there a way to do this such that whenever the items in MainCollection are update, so is this new 'pseudo-collection'.
Upvotes: 0
Views: 258
Reputation: 13535
You can create a FilteredCollection which listens on change events to update the filtered collection. This allows an efficient refiltering whenever the source collection has changed.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
namespace ConsoleApplication26
{
class FilteredObservableCollection<T> : INotifyCollectionChanged, IEnumerable<T>
{
List<T> _FilteredCached;
ObservableCollection<T> Source;
Func<T,bool> Filter;
public FilteredObservableCollection(ObservableCollection<T> source, Func<T,bool> filter)
{
Source = source;
Filter = filter;
source.CollectionChanged += source_CollectionChanged;
}
void source_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
var addedMatching = e.NewItems.Cast<T>().Where(Filter).ToList();
_FilteredCached.AddRange(addedMatching);
if (addedMatching.Count > 0)
{
CollectionChanged(sender, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, addedMatching));
}
}
else // make life easy and refresh fully
{
_FilteredCached = null;
CollectionChanged(sender, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
public IEnumerator<T> GetEnumerator()
{
if (_FilteredCached == null)
{
_FilteredCached = Source.Where(Filter).ToList(); // make it easy to get right. If someone would call e.g. First() only
// we would end up with an incomplete filtered collection.
}
foreach (var filtered in _FilteredCached)
{
yield return filtered;
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public event NotifyCollectionChangedEventHandler CollectionChanged = (o,e) => { };
}
class Program
{
static void Main(string[] args)
{
ObservableCollection<int> data = new ObservableCollection<int>(new int[] { 1, 2, 3, 4, 1 });
var filteredObservable = new FilteredObservableCollection<int>(data, x => x > 2);
Print(filteredObservable); // show that filter works
data.Add(1);
Print(filteredObservable); // no change
data.Add(10);
Print(filteredObservable); // change
data.Clear();
Print(filteredObservable); // collection is empy
data.Add(5);
Print(filteredObservable); // add item in filter range
data[0] = 1;
Print(filteredObservable); // replace it
}
static void Print<T>(FilteredObservableCollection<T> coll)
{
Console.WriteLine("Filtered: {0}", String.Join(",", coll));
}
}
}
This will print
Filtered: 3,4
Filtered: 3,4
Filtered: 3,4,10
Filtered:
Filtered: 5
Filtered:
Upvotes: 0
Reputation: 43036
Perhaps you are looking for the CollectionView class:
Represents a view for grouping, sorting, filtering, and navigating a data collection.
However,
You should not create objects of this class in your code. To create a collection view for a collection that only implements IEnumerable, create a CollectionViewSource object, add your collection to the Source property, and get the collection view from the View property.
So maybe the best place to start would be How to: Sort and Group Data Using a View in XAML. This and a few other how-to articles can be found at the bottom of the CollectionView page.
Upvotes: 1
Reputation: 14994
In other words, you would like to create a view of you MainCollection. That sounds like a job for LINQ!
var newCollection = from item in MainCollection
group item by /*item condintion */ into g //use where for filtering
select new { Prop = g.Prop, Item = g };
If you need observable then just simply pass the sequence to the ctor:
var observableColl = new ObservableCollection(newCollection);
Upvotes: 0
Reputation: 44275
Update the MainCollection to add this grouping ability
class MainCollection
{
public Dictionary<TGroupBySingleParameter, TValue> TheLookup{get; private set;}
Update()
{
TheLookup.Add(//...
//do work
}
}
Upvotes: 0
Reputation: 15579
There are a few open source frameworks for achieving this. I have used BindableLinq with some success. Though as noted on the home page development has been stalled on it and there are other framework alternatives.
These libraries are designed to offer updates when dependencies at numerous levels updated (e.g. the collection itself, or a property of an item that collection depends upon, or even an external dependency).
Upvotes: 1
Reputation: 124632
Would it be possible to simply expose a property that creates a new collection on demand? Something like
public List<Whatever> Items
{
get
{
return MainCollection.Where( x => [someCondition] ).ToList();
}
}
Upvotes: 0