keynesiancross
keynesiancross

Reputation: 3529

Collection that references items of other collection

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

Answers (6)

Alois Kraus
Alois Kraus

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

phoog
phoog

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

Lukasz Madon
Lukasz Madon

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

P.Brian.Mackey
P.Brian.Mackey

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

Reddog
Reddog

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

Ed Swangren
Ed Swangren

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

Related Questions