tags:

views:

263

answers:

3

I have a DataGridView that I want to use to store generic data. I want to keep a typed data list in the DataGridView class so that all of the sorts, etc. can be handled internally. But I don't want to have to set the type on the DataGridView since I won't know the data type until the InitializeData method is called.

public class MyDataGridView : DataGridView {
    private List<T> m_data;
    public InitializeData<T>(List<T> data) {
       m_data = data;
    }
    ... internal events to know when the datagrid wants to sort ...
    m_data.Sort<T>(...)
}

Is this possible? If so, how?

+1  A: 

If you won't know the type until you call InitializeData, then the type clearly can't be a compile-time part of the object.

Do you know everything you need to know about the sorting when you call InitializeData<T>? If so, how about you do something like:

private IList m_data;
private Action m_sorter;

public InitializeData<T>(List<T> data)
{
    m_data = data;
    // This captures the data variable. You'll need to
    // do something different if that's not good enough
    m_sorter = () => data.Sort();
}

Then when you need to sort later, you can just call m_sorter().

If you might sort on different things, you could potentially change it from an Action to Action<string> or whatever you'd need to be able to sort on.

Jon Skeet
The data will be sorted based on users clicking on various columns so the data needs to be accessed in methods and events after it's been initialized. Which is why I need to store it as a private member in the class.
ericmck
But how would those methods be able to operate on it in a strongly typed way if you don't know the type to start with?
Jon Skeet
Because I have a GenericComparer class that uses reflection to sort a generic list of objects. The properties that should be sorted are linked to the columns in the Data Grid View so I use the information from the columns clicked by the user, in combination with the GenericComparer to sort the list and show the data sorted.
ericmck
If it uses reflection, why do you need it to be strongly typed? Just store it as IList. Why does GenericComparer need to be strongly typed? Otherwise, could you pass the GenericComparer into InitializeData to start with?
Jon Skeet
You're right. No need to strongly type the list. I am now passing an array to the Data Grid InitializeData method that populates a local ArrayList. I use a GenericComparer that uses reflection on that ArrayList to sort the list based on the properties stored in the data grid columns. That gives me the functionality I want. Thanks.
ericmck
+1  A: 

If Jon's answer isn't sufficient, here's a more general (but more involved, and probably somewhat more confusing) approach:

/// <summary>
/// Allows a list of any type to be used to get a result of type TResult
/// </summary>
/// <typeparam name="TResult">The result type after using the list</typeparam>
interface IListUser<TResult>
{
    TResult Use<T>(List<T> list);
}

/// <summary>
/// Allows a list of any type to be used (with no return value)
/// </summary>
interface IListUser
{
    void Use<T>(List<T> list);
}

/// <summary>
/// Here's a class that can sort lists of any type
/// </summary>
class GenericSorter : IListUser
{
    #region IListUser Members

    public void Use<T>(List<T> list)
    {
        // do generic sorting stuff here
    }

    #endregion
}

/// <summary>
/// Wraps a list of some unknown type.  Allows list users (either with or without return values) to use the wrapped list.
/// </summary>
interface IExistsList
{
    TResult Apply<TResult>(IListUser<TResult> user);
    void Apply(IListUser user);
}

/// <summary>
/// Wraps a list of type T, hiding the type itself.
/// </summary>
/// <typeparam name="T">The type of element contained in the list</typeparam>
class ExistsList<T> : IExistsList
{

    List<T> list;

    public ExistsList(List<T> list)
    {
        this.list = list;
    }

    #region IExistsList Members

    public TResult Apply<TResult>(IListUser<TResult> user)
    {
        return user.Use(list);
    }

    public void Apply(IListUser user)
    {
        user.Use(list);
    }

    #endregion
}

/// <summary>
/// Your logic goes here
/// </summary>
class MyDataGridView
{
    private IExistsList list;
    public void InitializeData<T>(List<T> list)
    {
        this.list = new ExistsList<T>(list);
    }

    public void Sort()
    {
        list.Apply(new GenericSorter());
    }
}
kvb
A: 

You should define delgates or an interface for any generic operations you need to perform at runtime. As Jon Skeet mentioned, you can't strongly-type your data grid if you don't know the types at compile time.

This is the way the framework does it. For example:

Array.Sort();

Has a few ways it can be used:

  • Send it an array of objects that implement IComparable or IComparable<T>
  • Send in a second parameter, which is a class that implements IComparer or IComparer<T>. Used to compare the objects for sorting.
  • Send in a second parameter, which is a Comparison<T> delegate that can be used to compare objects in the array.

This is an example of how you approach the problem. At its most basic level, your scenario can be solved by a strategy pattern, which is what Array.Sort() does.

If you need to sort by things dynamically at run time, I would create an IComparer class that takes the column you want to sort by as an argument in its constructor. Then in your compare method, use that column as the sort type.

Here is an example of how you would do it using some basic example classes. Once you have these classes set up, then you'd pass both into your data grid and use them where appropriate.

public class Car
{
    public string Make { get; set; }
    public string Model { get; set; }
    public string Year { get; set; }
}

public class CarComparer : IComparer
{
    string sortColumn;

    public CarComparer(string sortColumn)
    {
        this.sortColumn = sortColumn;
    }

    public int Compare(object x, object y)
    {
        Car carX = x as Car;
        Car carY = y as Car;
        if (carX == null && carY == null)
            return 0;
        if (carX != null && carY == null)
            return 1;
        if (carY != null && carX == null)
            return -1;

        switch (sortColumn)
        {
            case "Make":
                return carX.Make.CompareTo(carY.Make);
            case "Model":
                return carX.Model.CompareTo(carY.Model);
            case "Year":
            default:
                return carX.Year.CompareTo(carY.Year);
        }
    }
}
Dan Herbert