Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sorting Datagridview datasource on List<T> where T is anonymous

A relatively simple question. I have a datagridview, which all it does is displays statistics. There is no editing/adding/deleting of rows. The datagridview is bound to a List. All I want to achieve is to have the user be able to sort the columns.

class Market
{
    public int Location {get;set;}
    public float Value {get;set;}
    //...
}
class City
{
    public String Name {get;set;}
    //...
}

List<Market> _markets;
List<City> _cities;
//Lists are populated.

dataGridView1.DataSource = _markets.Select(market => 
    new { _cities[market.Location].Name, market.Value}).ToList();

As expected, the columns are not sortable, but the information that is displayed is what is wanted. My question is how to make the DataGridView sort based on the column type with the least complicated and least amount of code, as the code will be used multiple times throughout.

This application used to use a DataBase that had views. These views then populated the DataGridViews. The views are still around, so a possible solution could be:

DataBase.ViewMarketValue temp = new DataBase.ViewMarketValue()

_markets.ForEach(market => temp.AddViewMarketValueRow(_cities[market.Location].Name, market.Value);
dataGridView1.DataSource = temp;

This results in the desired: a datagridview that has all the information and it is sortable. The only problem is that it seems wrong to use views in this aspect. So what should I do?

like image 899
Nick Babcock Avatar asked Jan 15 '11 20:01

Nick Babcock


2 Answers

In order to be able to sort the data automatically in the DataGridView, you need a collection that implements IBindingListView. In the BCL, the only classes that implement this interface are DataView and BindingSource (but the latter only supports sorting if the underlying datasource supports it too).

So, you have several options:

  • create a DataTable to hold the data, and bind it to the DataGridView (it will actually bind to the DefaultView of the DataTable)
  • create your own collection class that implements IBindingListView
  • use an existing implementation, like the AdvancedList<T> class posted by Marc Gravell in this post. You will also need to add a constructor to build the list from the result of your query:

    public AdvancedList(IEnumerable<T> collection)
    {
        foreach (var item in collection)
        {
            Add(item);
        }
    }
    

Since the result of your query is an anonymous type, you won't be able to call the constructor directly. The easiest way to work around the issue is to take advantage of type inference, by creating a generic method that will create the list. For convenience, you can create it as an extension method:

public static AdvancedList<T> ToAdvancedList<T>(this IEnumerable<T> source)
{
    return new AdvancedList<T>(source);
}

You can then use it like that:

dataGridView1.DataSource = _markets.Select(market => 
    new { _cities[market.Location].Name, market.Value}).ToAdvancedList();
like image 103
Thomas Levesque Avatar answered Oct 16 '22 18:10

Thomas Levesque


This is what I did base on Thomas Levesque (https://stackoverflow.com/a/4702631/1720085) and Mary Hamlin (https://stackoverflow.com/a/5805044/1720085) answers:

1) Added an extension method to my static class called Funcs

static class Funcs
{
    public static DataTable ToDataTable<T>(this IList<T> data)
    {
        PropertyDescriptorCollection properties = 
            TypeDescriptor.GetProperties(typeof(T));
        DataTable table = new DataTable();
        foreach (PropertyDescriptor prop in properties)
            table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
        foreach (T item in data)
        {
            DataRow row = table.NewRow();
            foreach (PropertyDescriptor prop in properties)
                 row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
            table.Rows.Add(row);
        }
        return table;
    }
}

2) And use it like:

var query = _markets.Select(market => 
    new { _cities[market.Location].Name, market.Value}).ToList();
dataGridView1.DataSource = Funcs.ToDataTable(query);
like image 37
Daniel Bonetti Avatar answered Oct 16 '22 19:10

Daniel Bonetti