A Solution for Binding Silverlight DataGrid Columns to 2nd Level Collection



Supreet Tare posted a thread on MSDN Forum to help him binding a 2nd Level Collection in Silverlight DataGrid control. As he didn't find any good solution there, he asked me on Twitter to give a solution to him for the same. I did a small investigation and came up with a solution for him.

 

In this post, I am going to describe the problem, as well as solution to this. Hope this solution will be helpful to you too and hence sharing in my blog. Read more to find out the solution.

 

Problem Statement

Suppose we have a collection of Students which includes a string property called "Firstname" and another property called "Marks" of type Collection. Each Mark consists of Subject and Subject Mark for example. See the collection structure as shown below:

 

image

 

 

Now, if we assign the Student collection to a DataGrid directly, it shows the data as below:

 

image

 

So, we need a solution that will show each of the 2nd level items as a separate single column like this:

 

image

 

 

Solution

Here is my solution for the problem statement. I created an ObservableCollection<Student> with the following structure:

 

image

 

 

We need Value Converters now which will return proper Subject and Mark extracting from the Marks collection.

 

To do this, we need to create two converters. The first one will take the Marks collection as value, index no. as parameter and return the proper subject name. Here is the code implementation of the same:

 

image

 

 

Similarly, we need another converter to return the Mark from the collection based on the index no. Let's see the implementation of the converter here:

 

image

 

 

Once done, we need to use those converters in our XAML page for DataGrid column. First we will add the those converters as the static resource to the page. We will add the Students collection as the ItemsSource to the DataGrid and set the AutoGenerateColumns property of the DataGrid to false. As this will not create the columns automatically, we need to add the columns manually.

 

Let's add the columns one by one. The first column is the "Firstname" which is very easy to integrate. For the rest of the columns we will do a small trick to bind the 2nd level collection. Have a look into the following code:

 
<UserControl.Resources>
    <Test:SubjectValueConverter x:Key="SubjectValueConverter"/>
    <Test:SubjectMarkValueConverter x:Key="SubjectMarkValueConverter"/>
</UserControl.Resources>
 
<Grid x:Name="LayoutRoot" Background="White">
    <sdk:DataGrid Margin="8,46,8,53" ItemsSource="{Binding Students, ElementName=userControl}"
                                     AutoGenerateColumns="False">
        <sdk:DataGrid.Columns>
            <sdk:DataGridTextColumn Header="Firstname"
                Binding="{Binding Firstname}"/>
            <sdk:DataGridTextColumn Header="Sub 1" 
                Binding="{Binding Marks, Converter={StaticResource SubjectValueConverter}, ConverterParameter=0}"/>
            <sdk:DataGridTextColumn Header="Mark 1" 
                Binding="{Binding Marks, Converter={StaticResource SubjectMarkValueConverter}, ConverterParameter=0}"/>
            <sdk:DataGridTextColumn Header="Sub 2"
                Binding="{Binding Marks, Converter={StaticResource SubjectValueConverter}, ConverterParameter=1}"/>
            <sdk:DataGridTextColumn Header="Mark 2"
                Binding="{Binding Marks, Converter={StaticResource SubjectMarkValueConverter}, ConverterParameter=1}"/>
        </sdk:DataGrid.Columns>
    </sdk:DataGrid>
</Grid>

 

From the above code you can see that, we did data binding to Marks collection for rest of the columns. We added the proper converters to the binding with respect to the return type i.e. Subject and Subject Mark. We added a converter parameter which sets the index of Mark in the said collection. For subject 1 and mark 1, we will pass '0' as the converter parameter. For subject 2 and mark 2, we will pass '1' as the converter parameter and so on...

 

image

 

Now if you run the application, you will see proper value as shown below:

 

image

 

Hope this will help you if you are facing the similar issue. Let me know, if you have further queries on the same. Also, if you have any other solution to that, please let me know.

12 comments

  1. Hi Kunal,
    Thanks for helping me out. I will give this solution a try today. I've a couple of doubts though
    1. I need to create the columns & binding in code (c#) not in XAML so I hope there is a way I can create a new static resource & bind it to the columns using c# as you have shown in XAML
    2. You are passing the parameters 0 & 1 manually how will I handle this if number of students are unknown? If I create the columns inside foreach loop so that I get indexes to pass as parameters I might get new columns for all my students.

    Again these are just speculations let me give it a shot today & i will revert back with my findings.
    Thanks again for helping :)

    Regards
    Supreet

    ReplyDelete
  2. Hello Supreet,

    Regarding your doubts, here are my answers:

    1. If you understand the concept using XAML, it will be easier for you to implement the same in code behind. Creating a static resource in XAML is not difficult in code behind. You just need to create an instance of the object.

    2. Parameters 0 and 1 are not belonging to Students but the no. of object present inside your marks collection. If you have 4 Mark object (which includes Subject and SubjectMark), you have to pass 0, 1, 2, 3 respectively for each Mark entry. As you will do it using for loop, you will have a proper index to use that. Again this parameter is only for your marks collection.

    Hope, this will help you now. Let me know, if you have further doubts/issues. Cheers...

    ReplyDelete
  3. Hello Kunal,
    I do not exactly see the interest in your solution.
    It would be easier to just use the correct query to unfold the marks for each student.

    What would be more interesting are the foollowing cases:
    - Build the columns dynamically to handle the fact that you may not know how many marks eah student has
    - Show how to handle read/write marks
    - Use a pivot table to handle the student marks with students not having the same marks then the others...

    That describes my current problem pretty much ;)

    Any hints about how to solve my problem?
    Thanks a lot in advance,
    john.

    ReplyDelete
  4. Hi Kunal,
    I tried this but I am definitely missing something, don't know exactly what
    Well here is what I tried
    Binding bind = new Binding();
    bind.Converter = new SubjectNameValueConverter();
    bind.ConverterParameter = 0;
    bind.Mode = BindingMode.OneWay;

    dg.Columns.Add(new GridViewDataColumn
    {
    Header = "Subject Name",
    DataMemberBinding = bind
    });

    & my Converter looks like this..
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
    var sc = value as List;
    if (sc==null)
    {
    return null;
    }

    var index = System.Convert.ToInt32(parameter);
    return sc[index].SubjectName;
    }

    Could you please let me know what is it that I am missing?/ Doing wrong?

    Thanks for your help. :)
    Regards
    Supreet

    ReplyDelete
  5. Hello John,

    The solution was mainly given for the problem statement that Supreet had. If you understand the implementation using XAML (no doubt, understanding the xaml implementation is easy), it will be easier for you to create the columns dynamically.

    Regarding your other query, this is just a small & simple solution. You need to use your own logic to resolve your business requirement.

    ReplyDelete
  6. Hi Supreet,

    I didn't find the Context in your code. You should bind proper data/collection to your column. Use bind.Path = new PropertyPath("Marks"); to set the context of the column.

    Here is my implementation of the same:

    Binding bind = new Binding();
    bind.Path = new PropertyPath("Marks");
    bind.Converter = new SubjectValueConverter();
    bind.ConverterParameter = 0;
    bind.Mode = BindingMode.OneWay;

    dg.Columns.Add(new DataGridTextColumn
    {
    Header = "Subject Name",
    Binding = bind
    });

    bind = new Binding();
    bind.Path = new PropertyPath("Marks");
    bind.Converter = new SubjectValueConverter();
    bind.ConverterParameter = 1;
    bind.Mode = BindingMode.OneWay;

    dg.Columns.Add(new DataGridTextColumn
    {
    Header = "Subject Name",
    Binding = bind
    });

    Hope this will help you to understand the issue and the solution. Cheers...

    ReplyDelete
  7. Just one thing to say...
    You are AWESOME :D :D I couldn't have done that myself ever..
    Thanks a ton mate. We really appreciate your help.

    Regards
    Supreet

    ReplyDelete
  8. You are welcome buddy. Hope that solved your problem :)

    ReplyDelete
  9. Hi Kunal, there's a way to adjust the width of the datagrid to the width of the content, to not display a blank column at the right.
    I'm trying put width="Auto", but doesn't work.

    ReplyDelete
  10. Hi you just set last column Width="*" instead of 'auto', den extra column at right side wil not b shown...

    ReplyDelete
  11. I was reading your article and I would like to appreciate you for making it very simple and understandable. This article gives me a basic idea of Data binding with datagrid control in silverlight. Check this links too its also having nice post with wonderful explanation on data binding with datagrid control in silverlight....
    http://www.c-sharpcorner.com/uploadfile/mahesh/working-with-datagrid-in-silverlight/

    http://blogs.msdn.com/b/scmorris/archive/2008/03/27/defining-columns-for-a-silverlight-datagrid.aspx

    http://mindstick.com/Articles/f5a53bd0-ab3f-47d3-b559-1a1a64d28b1e/?Data%20Binding%20with%20DataGrid%20control

    Thank Everyone!!

    ReplyDelete
  12. Thanks Pravesh for sharing the links.

    ReplyDelete


 
© 2008-2014 Kunal-Chowdhury.com | Designed by Kunal Chowdhury
Back to top