2
votes

I want to bind a collection of objects to a DataGrid in Silverlight. The objects belong to the following type:

public class Seats
{
    Dictionary<Group, long> dctValues = new Dictionary<Group, long>();

    public int Id { get; set; }

    public Dictionary<Group, long> Values
    {
        get { return dctValues; }
    }
}

Whereas, Group is represented by:

public class Group
{
    public int Id { get; set; }
    public string Name { get; set; }
}

I want to be able to generate columns based on the dictionary of groups, where each column would have the header set to Group.Name and the cell value for each item equal to the long value in the dictionary.

1
So far we have a header a column and a cell value in that column. How many other cells would you expect the column to have? IOW where do the rows come from?AnthonyWJones
Let's say we have List<Seats> that contains 5 items, where each will have 3 groups having their name property respectively "GroupA", "GroupB" and "GroupC". The 5 items are divided as follow: Item1: Id: 1, GroupA: 100, GroupB: 200, GroupC: 300 Item2: Id: 2, GroupA: 1000, GroupB: 2000, GroupC: 3000SiN
ok next question which may take a little thinking about. Two different instances of Seats (each a member of this List<Seats>) both have an entry in their respective Values dictionary for a Group that has the name "GroupA". Do they both reference a single instance of Group or do they each have their own different instance of Group that both happen to have the same value for Id and Name?AnthonyWJones

1 Answers

4
votes

I'm going to assume that we can't guarantee that there is only a single instance of a Group class for each group name. (Else you would be generating columns based on a list of known groups no?)

Here is a class derived from DataGrid:

public class SeatsGrid : DataGrid, IValueConverter
{
    public SeatsGrid()
    {
        AutoGenerateColumns = false;
    }
    #region public List<Seats> SeatsList

    public List<Seats> SeatsList
    {
        get { return GetValue(SeatsListProperty) as List<Seats>; }
        set { SetValue(SeatsListProperty, value); }
    }

    public static readonly DependencyProperty SeatsListProperty =
        DependencyProperty.Register(
            "SeatsList",
            typeof(List<Seats>),
            typeof(SeatsGrid),
            new PropertyMetadata(null, OnSeatsListPropertyChanged));

    private static void OnSeatsListPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        SeatsGrid source = d as SeatsGrid;
        List<Seats> value = e.NewValue as List<Seats>;
        source.OnSeatsListPropertyChanged(value);
    }

    private void OnSeatsListPropertyChanged(List<Seats> value)
    {
        ItemsSource = null;
        Columns.Clear();

        var groups = value
            .SelectMany(seats => seats.Values.Keys)
            .Select(g => g.Name)
            .Distinct();

        foreach (var group in groups)
        {
            DataGridTextColumn col = new DataGridTextColumn();
            col.Binding = new Binding()
            {
                Converter = this,
                ConverterParameter = group
            };
            col.Header = group;
            Columns.Add(col);
        }

        ItemsSource = value;
    }
    #endregion public List<Seats> SeatsList

    object IValueConverter.Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Seats seats = (Seats)value;
        string group = (string)parameter;

        Group actualGroup = seats.Values.Keys.FirstOrDefault(g => g.Name == group);
        if (actualGroup != null)
        {
            return seats.Values[actualGroup].ToString();
        }
        else
        {
            return null;
        }
    }

    object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Place an instance of this class in Xaml and assign a List<Seats> to its SeatsList property and it generates the columns and renders the rows.

How does it work?

The magic starts in the OnSeatsListPropertyChanged method. It first gets a list of distinct Group names. It generates a new Text column for each group name setting the header naturally to the group name.

The weird stuff appears when setting the binding for the column. The binding is given a converter which for convience I decided to implement on the SeatsGrid class as well. The converter parameter is the group name. Since no path is specified the whole Seats object will be passed to the converter when binding actually occurs.

Now looking at the IValueConverter.Convert method. It finds an instance (is any) of Group in the seats that has the same name as the converter parameter. If found uses that Group as the key to lookup a value to return.

If the Groups were known to be unique per name then the code can be simplified but the principle is the same.