If you want to define the number and the type of Silverlight DataGrid columns at runtime you can use the following approach. The technique can actually be used not only for Silverlight but also anywhere where you have to transform IDictionary (for example Dictionary or Hashtable, SortedDictionary etc) into anonymous typed object with each dictionary key turned into an object property.

You may say that this way I can violate the object constraints. For example each IDictionary inside IEnumerable can have different set of keys. And to a certain degree I can agree with that and if you rely on the data to come from a third party I wouldn’t recommend this approach or at least I would advice you to validate each IEnumerable entry before binding it to the control. But if you have a complete control on the transformation of the data – this is definitely a good approach. You can think of this as a way to use C# as a dynamic language. In the current solution I check the keys in the first entry of IEnumerable and if the second has more keys they will be ignored or if it has less the default values will be passed (null, Guid.Empty etc.)

So here is how your code can look. This is the code behind of an Xaml page:

public partial class Page : UserControl
    {
        public Page()
        {
            InitializeComponent();

            this.theGrid.Columns.Add(
                        new DataGridTextColumn
                        {
                            Header = "ID",
                            Binding = new Binding("ID")
                        });
            this.theGrid.Columns.Add(
                        new DataGridTextColumn
                        {
                            Header = "Name", 
                            Binding = new Binding("Name")
                        });
            this.theGrid.Columns.Add(
                        new DataGridTextColumn
                        {
                            Header = "Index",
                            Binding = new Binding("Index")
                        });
            this.theGrid.Columns.Add(
                        new DataGridTextColumn
                        {
                            Header = "Is Even",
                            Binding = new Binding("IsEven")
                        });
            this.theGrid.ItemsSource = GenerateData().ToDataSource();
        }

        public IEnumerable<IDictionary> GenerateData()
        {
            for(var i = 0; i < 15; i++)
            {
                var dict = new Dictionary<string, object>();
                dict["ID"] = Guid.NewGuid();
                dict["Name"] = "Name_" + i;
                dict["Index"] = i;
                dict["IsEven"] = ( i%2 == 0);
                yield return dict;
            }
        }
    }

Or in Visual Basic:

    ...
Public Function GenerateData() As IEnumerable(Of IDictionary) 
    Dim list As New List(Of IDictionary)() 
    For i As Integer = 0 To 9 
        Dim dict As IDictionary = New Dictionary(Of String, Object)() 
        dict("ID") = Guid.NewGuid()
        dict("Name") = "Name_" & i.ToString()
        dict("Index") = i
        dict("IsEven") = (i Mod 2 = 0)
        list.Add(dict) 
    Next 
    Return list 
End Function

    

The Xaml can be as simple as that:

    <Grid x:Name="LayoutRoot" Background="White">
        <data:DataGrid x:Name="theGrid" 
                Grid.Column="0" 
                Grid.Row="0" 
                AutoGenerateColumns="False">
        </data:DataGrid>
    </Grid>

As you can see the IEnumerable of IDictionary has no ToDataSource() method so we have to define this extension method and there is where the magic happens.

C# source: DataSourceCreator.cs; Visual Basic source: DataSourceCreator.vb

If you are looking for the implementation that generates objects implementing INotifyPropertyChanged you can find it here: www.bodurov.com/files/DataSourceCreator_INotifyPropertyChanged.zip

If you want to use ObservableCollection you would have to replace this row

var listType = typeof(List<>).MakeGenericType(new[] { objectType }); 

with this row:

var listType = typeof(ObservableCollection<>).MakeGenericType(new[] { objectType });

To read changed by the user value use:

private void Button_Click(object sender, RoutedEventArgs e)
{
    var list = this.theGrid.ItemsSource.Cast<object>().ToList();

    var obj = list[2];// user edits the third row
    var id = (int)obj.GetType().GetProperty("ID").GetValue(obj, null);
    var name = obj.GetType().GetProperty("Name").GetValue(obj, null) as string;
    var isEven = (bool)obj.GetType().GetProperty("IsEven").GetValue(obj, null);
}

And this is how all the magic is actually done:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Text.RegularExpressions;

namespace com.bodurov
{
    public static class DataSourceCreator
    {
        private static readonly Regex PropertNameRegex =
                new Regex(@"^[A-Za-z]+[A-Za-z0-9_]*$", RegexOptions.Singleline);

        private static readonly Dictionary<string, Type> _typeBySigniture =
                new Dictionary<string, Type>();


        public static IEnumerable ToDataSource(this IEnumerable<IDictionary> list)
        {
            IDictionary firstDict = null;
            bool hasData = false;
            foreach (IDictionary currentDict in list)
            {
                hasData = true;
                firstDict = currentDict;
                break;
            }
            if (!hasData)
            {
                return new object[] { };
            }
            if (firstDict == null)
            {
                throw new ArgumentException("IDictionary entry cannot be null");
            }

            string typeSigniture = GetTypeSigniture(firstDict, list);

            Type objectType = GetTypeByTypeSigniture(typeSigniture);

            if (objectType == null)
            {
                TypeBuilder tb = GetTypeBuilder(typeSigniture);

                ConstructorBuilder constructor =
                            tb.DefineDefaultConstructor(
                                        MethodAttributes.Public |
                                        MethodAttributes.SpecialName |
                                        MethodAttributes.RTSpecialName);


                foreach (DictionaryEntry pair in firstDict)
                {
                    if (PropertNameRegex.IsMatch(Convert.ToString(pair.Key), 0))
                    {
                        CreateProperty(tb,
                                        Convert.ToString(pair.Key),
                                        GetValueType(pair.Value, list, pair.Key));
                    }
                    else
                    {
                        throw new ArgumentException(
                                    @"Each key of IDictionary must be 
                                alphanumeric and start with character.");
                    }
                }
                objectType = tb.CreateType();

                _typeBySigniture.Add(typeSigniture, objectType);
            }

            return GenerateEnumerable(objectType, list, firstDict);
        }



        private static Type GetTypeByTypeSigniture(string typeSigniture)
        {
            Type type;
            return _typeBySigniture.TryGetValue(typeSigniture, out type) ? type : null;
        }

        private static Type GetValueType(object value, IEnumerable<IDictionary> list, object key)
        {
            if(value == null)
            {
                foreach (var dictionary in list)
                {
                    if(dictionary.Contains(key))
                    {
                        value = dictionary[key];
                        if(value != null) break;
                    }
                }
            }
            return (value == null) ? typeof(object) : value.GetType();
        }

        private static string GetTypeSigniture(IDictionary firstDict, IEnumerable<IDictionary> list)
        {
            var sb = new StringBuilder();
            foreach (DictionaryEntry pair in firstDict)
            {
                sb.AppendFormat("_{0}_{1}", pair.Key, GetValueType(pair.Value, list, pair.Key));
            }
            return sb.ToString().GetHashCode().ToString().Replace("-", "Minus");
        }

        private static IEnumerable GenerateEnumerable(
                 Type objectType, IEnumerable<IDictionary> list, IDictionary firstDict)
        {
            var listType = typeof(List<>).MakeGenericType(new[] { objectType });
            var listOfCustom = Activator.CreateInstance(listType);

            foreach (var currentDict in list)
            {
                if (currentDict == null)
                {
                    throw new ArgumentException("IDictionary entry cannot be null");
                }
                var row = Activator.CreateInstance(objectType);
                foreach (DictionaryEntry pair in firstDict)
                {
                    if (currentDict.Contains(pair.Key))
                    {
                        PropertyInfo property =
                            objectType.GetProperty(Convert.ToString(pair.Key));
                            
                        var value = currentDict[pair.Key];
                        if (value != null && 
                            value.GetType() != property.PropertyType && 
                            !property.PropertyType.IsGenericType){
                              try
                              {                        
                                  value = Convert.ChangeType(
                                              currentDict[pair.Key],
                                              property.PropertyType,
                                              null); 
                              }catch{}
                        }

                        property.SetValue(row, value, null);
                    }
                }
                listType.GetMethod("Add").Invoke(listOfCustom, new[] { row });
            }
            return listOfCustom as IEnumerable;
        }

        private static TypeBuilder GetTypeBuilder(string typeSigniture)
        {
            AssemblyName an = new AssemblyName("TempAssembly" + typeSigniture);
            AssemblyBuilder assemblyBuilder =
                AppDomain.CurrentDomain.DefineDynamicAssembly(
                    an, AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");

            TypeBuilder tb = moduleBuilder.DefineType("TempType" + typeSigniture
                                , TypeAttributes.Public |
                                TypeAttributes.Class |
                                TypeAttributes.AutoClass |
                                TypeAttributes.AnsiClass |
                                TypeAttributes.BeforeFieldInit |
                                TypeAttributes.AutoLayout
                                , typeof(object));
            return tb;
        }

        private static void CreateProperty(
                        TypeBuilder tb, string propertyName, Type propertyType)
        {
            if(propertyType.IsValueType && !propertyType.IsGenericType)
            {
                propertyType = typeof(Nullable<>).MakeGenericType(new[] { propertyType });
            }

            FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName,
                                                        propertyType,
                                                        FieldAttributes.Private);


            PropertyBuilder propertyBuilder =
                tb.DefineProperty(
                    propertyName, PropertyAttributes.HasDefault, propertyType, null);
            MethodBuilder getPropMthdBldr =
                tb.DefineMethod("get_" + propertyName,
                    MethodAttributes.Public |
                    MethodAttributes.SpecialName |
                    MethodAttributes.HideBySig,
                    propertyType, Type.EmptyTypes);

            ILGenerator getIL = getPropMthdBldr.GetILGenerator();

            getIL.Emit(OpCodes.Ldarg_0);
            getIL.Emit(OpCodes.Ldfld, fieldBuilder);
            getIL.Emit(OpCodes.Ret);

            MethodBuilder setPropMthdBldr =
                tb.DefineMethod("set_" + propertyName,
                  MethodAttributes.Public |
                  MethodAttributes.SpecialName |
                  MethodAttributes.HideBySig,
                  null, new Type[] { propertyType });

            ILGenerator setIL = setPropMthdBldr.GetILGenerator();

            setIL.Emit(OpCodes.Ldarg_0);
            setIL.Emit(OpCodes.Ldarg_1);
            setIL.Emit(OpCodes.Stfld, fieldBuilder);
            setIL.Emit(OpCodes.Ret);

            propertyBuilder.SetGetMethod(getPropMthdBldr);
            propertyBuilder.SetSetMethod(setPropMthdBldr);
        }
    }
}

Sample project: http://www.bodurov.com/files/GridTestSilverlight.zip

Code available under MIT License, that means this

Share this post:   digg     Stumble Upon     del.icio.us     E-mail

Jen
Posted on 5/13/2008 1:45:22 PM

So how about the performance implications of that approach?

Vladimir Bodurov
Posted on 5/13/2008 5:08:50 PM

The type is generated once not per each record, so I don't think this should be of a great concern.

Ethan Nagel
Posted on 5/19/2008 4:47:13 PM

Wow, very well done! I struggled with this problem and ended up with a much less elegant solution. (Basically create an IValueConverter that returns an index into a dictionary -- using the parameter as the index.)

I have to say I'm impressed with what you've done here and the fact that Silverlight is powerful enough to allow this approach.

Excellent job!

- Ethan

Vladimir Bodurov
Posted on 5/19/2008 6:06:00 PM

Thank you Ethan, it is truly amazing how powerful .NET is and now we have a lot of this power on the client side with Silverlight!

.NET is strongly typed but as a matter of fact with the extension methods, anonymous types and all the other new features it can be as powerful as the dynamic languages.

samcov
Posted on 5/20/2008 11:32:21 PM

Impressive, both code wise, and performance wise.

I first tried 100 records, fearing that the reflection would be slow... it came right up. Then I tried 500, same result.

Finally, I tried 1000, which is more than I would use(we always prefer to page results), and it still came up very fast.

GREAT WORK!!!

Vladimir Bodurov
Posted on 5/21/2008 12:47:54 AM

Thanks samcov,

A possible slow down due to the reflection will be in the number of columns (or properties generated), while the rows are handled after the type generation has been completed. But I don’t see why you may need a huge number of columns.

nmpgaspar
Posted on 5/21/2008 3:06:13 AM

thanks for that info dude, however i'm having the following problem:

Error 1 Using the generic type 'System.Collections.Generic.IDictionary<TKey,TValue>' requires '2' type arguments

at: public IEnumerable<IDictionary> GenerateData() -> Page.xaml.cs

How do i fix that?

nmpgaspar
Posted on 5/21/2008 3:26:07 AM

Ok i was missing System.Collections
working now :)

jercra
Posted on 6/1/2008 10:39:24 PM

This took me a week to find and is exactly what I was looking for. Thank you very much. I have converted your code to VB for anyone who is interested.

Imports Microsoft.VisualBasic  
Imports System.Reflection.Emit  
Imports System.Reflection  
Imports System  
Imports System.Collections  
Imports System.Collections.Generic  
Imports System.Text.RegularExpressions  
  
Public Class DataSourceCreator  
    Private ReadOnly Property PropertNameRegex() As Regex  
        Get  
            Return New Regex("^[A-Za-z]+[A-Za-z1-9_]*$", RegexOptions.Singleline)  
        End Get  
    End Property  
  
  
    Public Function ToDataSource(ByVal list As IEnumerable(Of IDictionary)) As Object()  
        Dim firstDict As IDictionary = Nothing  
        Dim hasData As Boolean = False  
        Dim currentDict As IDictionary  
        For Each currentDict In list  
            hasData = True  
            firstDict = currentDict  
            Exit For  
        Next  
  
        If hasData = False Then  
            Return New Object() {}  
        End If  
        If firstDict Is Nothing Then  
            Throw New ArgumentException("IDictionary entry cannot be null")  
        End If  
  
        Dim objectType As Type = Nothing  
        Dim tb As TypeBuilder = GetTypeBuilder(list.GetHashCode())  
  
        Dim constructor As ConstructorBuilder = tb.DefineDefaultConstructor(MethodAttributes.Public Or MethodAttributes.SpecialName Or MethodAttributes.RTSpecialName)  
        Dim pair As DictionaryEntry  
        For Each pair In firstDict  
            If (PropertNameRegex.IsMatch(Convert.ToString(pair.Key), 0)) Then  
                CreateProperty(tb, Convert.ToString(pair.Key), pair.Value.GetType)  
            Else  
                Throw New ArgumentException("Each key of IDictionary must be alphanumeric and start with character. :: key=" & pair.Key & " value=" & pair.Value)  
            End If  
        Next  
        objectType = tb.CreateType()  
        Return GenerateArray(objectType, list, firstDict)  
    End Function  
  
    Private Function GenerateArray(ByVal objectType As Type, ByVal list As IEnumerable(Of IDictionary), ByVal firstDict As IDictionary) As Object()  
        Dim itemsSource = New List(Of Object)()  
        Dim currentDict As IDictionary  
        For Each currentDict In list  
  
            If currentDict Is Nothing Then  
                Throw New ArgumentException("IDictionary entry cannot be null")  
            End If  
            Dim row As Object = Activator.CreateInstance(objectType)  
            Dim pair As DictionaryEntry  
            For Each pair In firstDict  
                If (currentDict.Contains(pair.Key)) Then  
                    Dim [property] As PropertyInfo = objectType.GetProperty(Convert.ToString(pair.Key))  
                    [property].SetValue(row, Convert.ChangeType(currentDict(pair.Key), [property].PropertyType, Nothing), Nothing)  
                End If  
            Next  
            itemsSource.Add(row)  
        Next  
        Return itemsSource.ToArray()  
    End Function  
  
    Private Function GetTypeBuilder(ByVal code As Integer) As TypeBuilder  
        Dim an As AssemblyName = New AssemblyName("TempAssembly" & code)  
        Dim assemblyBuilder As AssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run)  
        Dim moduleBuilder As ModuleBuilder = assemblyBuilder.DefineDynamicModule("MainModule")  
  
        Dim tb As TypeBuilder = moduleBuilder.DefineType("TempType" & code, TypeAttributes.Public Or TypeAttributes.Class Or TypeAttributes.AutoClass Or _  
                                                          TypeAttributes.AnsiClass Or TypeAttributes.BeforeFieldInit Or TypeAttributes.AutoLayout, GetType(Object))  
        Return tb  
    End Function  
  
    Private Sub CreateProperty(ByVal tb As TypeBuilder, ByVal propertyName As String, ByVal propertyType As Type)  
        Dim fieldBuilder As FieldBuilder = tb.DefineField("_" & propertyName, propertyType, FieldAttributes.Private)  
        Dim propertyBuilder As PropertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, Nothing)  
        Dim getPropMthdBldr As MethodBuilder = tb.DefineMethod("get_" & propertyName, MethodAttributes.Public Or MethodAttributes.SpecialName _  
                Or MethodAttributes.HideBySig, propertyType, Type.EmptyTypes)  
  
        Dim getIL As ILGenerator = getPropMthdBldr.GetILGenerator()  
  
        getIL.Emit(OpCodes.Ldarg_0)  
        getIL.Emit(OpCodes.Ldfld, fieldBuilder)  
        getIL.Emit(OpCodes.Ret)  
  
  
        Dim setPropMthdBldr As MethodBuilder = tb.DefineMethod("set_" & propertyName, MethodAttributes.Public Or MethodAttributes.SpecialName Or _  
                                                                MethodAttributes.HideBySig, Nothing, New System.Type() {propertyType})  
  
        Dim setIL As ILGenerator = setPropMthdBldr.GetILGenerator()  
  
        setIL.Emit(OpCodes.Ldarg_0)  
        setIL.Emit(OpCodes.Ldarg_1)  
        setIL.Emit(OpCodes.Stfld, fieldBuilder)  
        setIL.Emit(OpCodes.Ret)  
  
        propertyBuilder.SetGetMethod(getPropMthdBldr)  
        propertyBuilder.SetSetMethod(setPropMthdBldr)  
    End Sub  
End Class 
jercra
Posted on 6/1/2008 11:11:16 PM

Any interest in showing how one could take the resulting object and make it serializable for LINQ processing or so you can generate the objects on the fly with a web service and pass them to silverlight that way? Even a nudge in the right direction would be a great help.

Thanks.

Vladimir Bodurov
Posted on 6/2/2008 12:08:58 AM

Thanks for the VB code. Anything that implements IEnumerable<T> is querable by LINQ if you want to have higher capabilities for quering you will need to make it implement IQueryable http://msdn.microsoft.com/en-us/library/system.linq.iqueryable.aspx

If you want the object to be returned by WCF service you'd have to twick reflection code to add System.Runtime.Serialization.DataContractAttribute to the object and System.Runtime.Serialization.DataMemberAttribute for each property.

Doug S
Posted on 6/20/2008 2:24:11 AM

Very nice, but when I try this on a grid in slb2 and click a column to sort it you get.

SortDescription's property name "Name" is invalid.
at System.Windows.Controls.ListCollectionView.GetPropertyType(Type parentType, String propertyPath)
at System.Windows.Controls.SortFieldComparer`1.CreatePropertyInfo(SortDescriptionCollection sortFields)
at System.Windows.Controls.SortFieldComparer`1..ctor(SortDescriptionCollection sortFields)

Vladimir Bodurov
Posted on 6/20/2008 11:20:38 AM

These are the new features of the latest silverlight release. Sorting cannot look at real type behind object[]. So please take the current code on the blog where I replaces object[] with IEnumerable.

Nico Barten
Posted on 7/4/2008 7:46:12 AM

Vladimir, do you maybe know how to convert the 'yield return' thing to VB code? I know VB .NET doesn't have the keyword yield, but i really need the code in VB (and i don't understand yield at all)

Vladimir Bodurov
Posted on 7/4/2008 8:42:24 AM

Try this:

Public Function GenerateData() As IEnumerable(Of IDictionary)   
    Dim list As New List(Of Dictionary(Of String, Object))()   
    For i As Integer = 0 To 14   
        Dim dict As IDictionary = New Dictionary(Of String, Object)()   
        dict.Add("ID", Guid.NewGuid())   
        dict.Add("Name", "Name_" + i)   
        dict.Add("Index", i)   
        dict.Add("IsEven", i Mod 2 = 0)   
        list.Add(dict)   
    Next   
    Return list   
End Function   
Vladimir Bodurov
Posted on 7/14/2008 11:22:37 AM
petel
Posted on 7/17/2008 5:57:46 AM

@jercra

Did you ever manage to serialize an object and pass it over to silverlight, I'm halfway through figuring this out at the moment, but stuck as to why wcf doesn't seem to want to serialize my object, I cannot see any obvious reason as to why it is not (I'm sure I'm doing something silly somewhere).

Btw great article Vladimir! Thank you!

Vladimir Bodurov
Posted on 7/17/2008 1:16:16 PM

Thanks Petel,

Silverlight and usual .NET are different frameworks even though they are designed to look similar. But if you create proxy you can easily create a silverlight version of the server object. For the sake of experiment it is quite interesting to try serializing and desterilising an object between the two frameworks, but I haven’t tried that. I am not sure this is possible.

Antoine habert
Posted on 7/22/2008 2:36:43 PM

It's really G R E A T !

I was struggling with this kind of problem for some days now, and you brings the nice and clean solution I was exactly looking for. Thank you !

John Kirby
Posted on 8/6/2008 5:23:49 AM

Vladimir,

Neat solution that works well!

What I don't understand is how you can access the Data Grid's ItemsSource property. When a grid is editable then, as I understand it, the ItemsSource is updated when a cell is edited. I want to get access to the edited ItemsSource to iterate over the rows and cells. I thought I would be able to do this by casting the ItemsSource back to the original IEnumerable<IDictionary> type that I used to populate the grid. However, this cast doesn't work and my knowledge of reflection isn't good enough. Any ideas or suggestions?

Thanks

John

Vladimir Bodurov
Posted on 8/6/2008 9:57:50 AM

It is not IEnumerable<IDictionary> after the ToDataSource method. you can get the changed value as shown here

private void TestBtn_Click(object sender, RoutedEventArgs e) 
{ 
    IEnumerable<object> collection = this.theGrid.ItemsSource.Cast<object>(); 
    List<object> list = collection.ToList(); 
    object name = list[0].GetType().GetProperty("Name").GetValue(list[0], null); 
    HtmlPage.Window.Alert("name: " + name); 
} 
John Kirby
Posted on 8/7/2008 7:17:02 AM

That works!

Thanks for your help and prompt reply!

Dima
Posted on 8/13/2008 11:03:07 AM

Your code creates Property type using “the first row” of data.
In some cases one ore more field can be null (System.DBNull) in the first row but not null for others rows. In that case property.SetValue generates an exception of conversion a type like System.Int32 to System.DBNull.
In generally, is that possible to have null in the Silverlight DataGrid? It would be nice to have.

There is another issue. It would be nice to have more readable column name, for example
“First name (given name)” instead of “First_Name”. I removed the matching of a column name and found out that binding doesn’t work for name that include ‘(‘, ‘)’.
‘.’ in the name did not generate an exception, but later I found out that the column was not populated at all.

Thank you.

Vladimir Bodurov
Posted on 8/13/2008 11:57:13 AM

Hi Dima,

Thanks for your comments. Convertion from and to DBNull is a concern of your database - business object mapper, not IDictionary - DataSource converter. You can read more about the separation ofconcerns here: http://en.wikipedia.org/wiki/Separation_of_concerns or just google it.

The name has current constraints because they are transformed to object properties and those are the constraints of the .NET properties. There should be no reasons why you couldn't work within those constraints as the property name is not something that is shown to the end user.

Dima
Posted on 8/15/2008 11:29:25 AM

Hi Vladimir,

Thank you for answer.

1) DbNull. My point was the code does not work properly with null at all. If the first element in a column is null than others element must be null.
I can not convert null to int( or double or DateTime). Actually, I can convert all elements in column to int? (double?, DateTiem?) that can accept null . However, the code should be modified a bit. I did it and it works fine.

2) I totally forgot that a Header name and an object property name could be different.
I fixed my code and now in my DataGrid user can see columns like “Price(before tax)”, “Price (with tax)” for property names like price_1, price_2.

Brian Lane
Posted on 10/16/2008 8:44:22 PM

Hi, have you tried this with the released version of Silverlight 2? After I updated to the released version, this no longer works for me.

Vladimir Bodurov
Posted on 10/17/2008 10:59:02 AM

Hi Brian,

I have Silverlight 2.0.30523.8 ( you can see the number at C:\Program Files\Microsoft Silverlight) and I can see no problems.

What is the version of your silverlight? What is the error message you get?

Brian Lane
Posted on 10/21/2008 7:11:56 PM

Hi Vladimir,

Thanks for your prompt response!

The version I am using is 2.0.31005.0 . It was working fine with Silverlight 2.0 Beta 2 but stopped working when I upgraded to the official release version of Silverlight 2.0

The error I am seeing in the debugger is that it is not recognizing the generated datasource as being valid. The message says "Could not evaluate expression" and there are no columns in the bound DataGrid even though there are columns in the original data. I am wondering if Silverlight 2 is no longer recognizing the dynamically generated types.

Copied from the debugger:

DataSource = Could not evaluate expression

Thanks,

Brian Lane

Vladimir Bodurov
Posted on 10/21/2008 11:42:29 PM

Hi Brian,

I just updated my Silverlight tools to the latest version. I seem to have no problems with the grid dynamic data binding.

I sent to your e-mail a test project with simple grid binding. Try it to see if this works for you. I can see no problem so far.

Vlad

Foontik
Posted on 10/23/2008 2:46:56 AM

Excellent job! Thank you!

Brian Lane
Posted on 10/23/2008 9:09:20 PM

Hi Vladmir,

Thanks for trying it out with the latest Silverlight and sending me the test project! I should have tried a test project like the one you sent me before thinking that the released version of Silverlight 2.0 was the culprit.

I tried your test project and it works fine for me as well. It looks like it is probably another third party Silverlight component that is not working. The debugger problem I was having made it difficult to figure out why the third party grid wasn't binding properly as it had in earlier versions of Silverlight.

Thanks again!

Brian

Mike
Posted on 11/3/2008 11:48:17 AM

I receive the "Could not evaluate expression" not when binding, but when attempting to reference the SelectedItem of the bound grid. This was not a problem prior to Silverlight 2.0 full release. Any ideas?

Vladimir Bodurov
Posted on 11/3/2008 12:07:11 PM

Here is a test project http://www.bodurov.com/files/GridTest.zip that works fine with 2.0 release

Vladimir Bodurov
Posted on 11/3/2008 12:20:07 PM

And if your question is how to access a property. Here is the answer:

public Page()  
{  
    InitializeComponent();  
  
    this.TheGrid.ItemsSource = GenerateData().ToDataSource();  
  
    this.TheGrid.MouseLeftButtonUp += this.TheGrid_SelectionChanged;  
}  
  
private void TheGrid_SelectionChanged(object sender, EventArgs e)  
{  
    var t = this.TheGrid.SelectedItem.GetType();  
    var s = (string)t.GetProperty("Name").GetValue(this.TheGrid.SelectedItem, null);   
    HtmlPage.Window.Alert("Name:" + s);  
} 
Mike
Posted on 11/5/2008 9:15:20 AM

I am attempting GetCellContent(myGrid.SelectedItem) and receive the "expression" error. I don't need the property, I need the cell.

Vladimir Bodurov
Posted on 11/5/2008 10:33:35 AM

I am not sure what is your issue man. It works just fine for me:

public partial class Page : UserControl 
{ 
    public Page() 
    { 
        InitializeComponent(); 
 
        this.TheGrid.ItemsSource = GenerateData().ToDataSource(); 
 
        this.TheGrid.MouseLeftButtonUp += this.TheGrid_SelectionChanged; 
    } 
 
    private void TheGrid_SelectionChanged(object sender, EventArgs e) 
    { 
        var element = (TextBlock)this.TheGrid.Columns[0].GetCellContent(this.TheGrid.SelectedItem); 
        HtmlPage.Window.Alert("element:" + element.Text); 
    } 
 
    public IEnumerable<IDictionary> GenerateData() 
    { 
        for (var i = 0; i < 15; i++) 
        { 
            var dict = new Dictionary<string, object>(); 
            dict["ID"] = Guid.NewGuid(); 
            dict["Name"] = "Name_" + i; 
            dict["Index"] = i; 
            dict["IsEven"] = (i % 2 == 0); 
            yield return dict; 
        } 
    } 
} 
Robert
Posted on 11/26/2008 10:26:47 AM

cool code man! I am trying to use this code in SL's AutoCompleteBox. It works very well except for when I type somthing in the box. It displays "TempType49212206" or something like it. I was wondering if you had any thoughts on how to override the ToString() method in the typed object?

thanks,
R

Vladimir Bodurov
Posted on 11/26/2008 11:08:07 AM

It is possible but that would polute th clean model that this solution follows now. Instead of that you better access the properties of TempType49212206

Gaurav
Posted on 11/26/2008 8:32:05 PM

Hi Vladimir,

Nice code. I am trying to implement it in my solution and am running into following issues.
1. In your code you are adding a "DataGridTextBoxColumn". Is this something that you wrote because I could not find it in SL library.

2. I ended up using a "DataGridTextColumn" but it does not have "DisplayMemberBinding".

Please let me know what I am doing wrong.

Thanks

Gaurav

Vladimir Bodurov
Posted on 11/26/2008 8:39:05 PM

Please see http://www.bodurov.com/files/GridTest.zip for a test project. Check the references.

Robert
Posted on 12/2/2008 8:41:54 AM

Could you give a short example on how to access the properties of TempType49212206?

Thanks,
R

Robert
Posted on 12/2/2008 10:14:20 AM

2nd question. When I add a watcher to "row" in this line:
"var row = Activator.CreateInstance(objectType);" I get a "Could not evaluate expression" in the watcher. I think this may be a limatation of Silverlight. do you have any thoughts on that?

Thanks,
R

Sorry about the double posts. not sure what happend.

Vladimir Bodurov
Posted on 12/2/2008 11:54:21 AM
public Page()   
{   
    InitializeComponent();   
   
    this.TheGrid.ItemsSource = GenerateData().ToDataSource();   
   
    this.TheGrid.MouseLeftButtonUp += this.TheGrid_SelectionChanged;   
}   
   
private void TheGrid_SelectionChanged(object sender, EventArgs e)   
{   
    var t = this.TheGrid.SelectedItem.GetType();   
    var s = (string)t.GetProperty("Name").GetValue(this.TheGrid.SelectedItem, null);    
    HtmlPage.Window.Alert("Name:" + s);   
}  
Robert
Posted on 12/2/2008 12:39:12 PM

Thank you Vladimir!
I found another way to make the autocompletebox work with your code and still maintain your "clean model". just set the converter property of the autocompletebox to this:

public class ValueConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{

if (value != null)
{

System.Reflection.PropertyInfo[] pi = value.GetType().GetProperties();

int last = pi.Count() - 1;
string ret = pi[last].GetValue(value, null).ToString();
return ret;

//Gets property by name//
//System.Reflection.PropertyInfo pi = value.GetType().GetProperty("Index");
//string test = pi.GetValue(value, null).ToString();
}
return null;
}

public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}

Thanks again for your help.

R

Rick
Posted on 12/18/2008 2:17:54 PM

@petel
@jercra

Did you ever manage to serialize an object and pass it over to silverlight? I am in the middle of figuring this out too.

Rick

Vladimir Bodurov
Posted on 12/19/2008 2:20:12 PM

No I always use wcf to transfer data, you probably should too, otherwise its like reinventing the wheel, though as something to play with it's may be an interesting thing. I have never tried that.

Karol Kolenda
Posted on 12/29/2008 4:43:41 AM

Hi Vladimir,
First of all thank you for your code - it works really great.
Just one tiny question re: memory allocation - every time I assign new dataset, your code creates a new type. Is this type will ever be garbage collected? If not, what is rough estimation of memory "leak" say for a dataset with 100 columns? Is it small enough so we can all forget about it?

Thanks,
Karol

Vladimir Bodurov
Posted on 12/29/2008 12:03:41 PM

Oh don't worry about the garbage collection. It works fine. I have set up a project that demonstrates that. You can get it from here: http://bodurov.com/files/GridTest_GarbageCollection.zip

As you can see I define finalizer (destructor) for the generated object. Then I define my custom FinalizedException and I throw it in the finalizer. Unfortunatelly you can not do something nicer here like showing an alert box because finalization happens on a thread different from the UI thread.

So you click the Reload Data button once, and then you wait. It may take a minute or two before garbage collection kicks out but usually it will be executed right away. And you get that exception for each line - each object.

andrus
Posted on 12/30/2008 12:34:31 PM

Why you re-invented the wheel ?

MS Dynamic Linq library already has CreateClass method which creates dynamic entity in same way:

http://weblogs.asp.net:80/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

Andrus.

Vladimir Bodurov
Posted on 12/30/2008 12:51:57 PM

Well it is a similar kind of concept but there you would pass a list of DynamicProperty objects which is not as convenient and easy to use as IDictionary. So with all my respect it is similar but not the same.

But thanks for the link for whoever wants to use it!

Andrus
Posted on 1/12/2009 10:14:06 AM

How to return results of anon DLinq query from ASMX Web service to display in DataGrid using ToDataSource() or other method?

I tried code below but web service call does not return any properties.
Should we to return some dictionary object ?
I need ASMX since SVC service is not supporrted in MONO.

Andrus.

[WebService]
public sealed class StockService {

[WebMethod]
public object[] GetProductList(string table, int pageNumber, int itemsPerPage, string filter, string sortBy, out int totalCount)
{
using (var db = new Database())
{

swithch (Table) {
case "Customer":
totalCount = db.Customer.Count();

var l = db.Customer.Skip(pageNumber*itemsPerPage).Take(itemsPerPage ).
Select((c) => new { c.Id, c.Name}).ToArray();
return l;

case "Product":
totalCount = db.Product.Count();
var l = db.Product.Skip(pageNumber*itemsPerPage).Take(itemsPerPage ).
Select((p) => new { p.IdNo, p.Name, p.Address}).ToArray();
return l;
}
}
}
}

Vladimir Bodurov
Posted on 1/12/2009 10:41:20 AM

Well your question is actually not about binding but about calling a service. You need to have your classes decorated with attributes. This may help: http://blog.bodurov.com/Create-a-WCF-client-for-asmx-web-service-without-using-web-proxy

Andrus
Posted on 1/16/2009 1:50:56 PM

How to update data edited using this method in server ?

Since there is no INotofyPropertychanged implementation changes cannot dedected in usual way. Should we add single property RowState: Added, Changed, NotChanged to dictionary and set this property in DataGrid events or any other idea ? Or is it bettet to change this code to implement INOfifyPropertyChanged also ?

Andrus
Posted on 1/18/2009 7:22:28 AM

I need to edit generated entity list in DataGrid.

I tried to use this converter but it does not generate type based on ObservableCollection<T>.

How to change this code so that generated type derives from ObservableCollection<T> or any other idea to edit dynamic column data ?

Vladimir Bodurov
Posted on 1/18/2009 5:36:38 PM

I guess you can change the List<T> in the code to ObservableCollection<T> but I don't have time these days to test how it works. If you have questions you have to ask them on the forums.asp.net I think.

Carlos Oliveira
Posted on 1/21/2009 9:22:44 AM

Hi Vlad,

Firstly, great work, your solution is brilliantly simple yet very powerful.

However, I have a small problem...I need to build a dynamic object and then bind it to a RadGrid, but I am noticing a definite memory leak with the created assemblies.

In the GC example you have given above, I’d expect the instance of the type to get collected; the problem is that the assembly that hosts the type won’t. Over time, the process fills up with assemblies, even though the number of reachable objects in .net can remain very small.

Do you have any views on how the assemblies can be kept under control?

Thanks,

Carlos

Vladimir Bodurov
Posted on 1/21/2009 2:39:46 PM

Hi Carlos,

I have updated the code to include type caching.

Carlos Oliveira
Posted on 1/22/2009 4:58:31 AM

Hey Vlad,

Thanks for such a quick response! The new caching works great and the memory usage is now much more controlled....My worry is that the number of different reports I will be displaying in the radgrid could be in the thousands so the memory could still get unwieldy.

From what I've seen my only real option is to create the assemblies in their own AppDomain and then Unload that domain at certain intervals...the other idea was to try and recreate this functionality in a Dynamic Method so it is seen by the GC but my IL isn't strong enough and I'm not even sure if it is possible as you would still need the Type?

If you have any advice on the best way to proceed that would be much appreciated, but thanks again for what you have already done!.

Carlos

Vladimir Bodurov
Posted on 1/22/2009 10:58:36 AM

Why do you worry so much about this Carlos? Those assemblies are on the client because Silverlight is on the client, also they are in the temp folder and once unloaded they will be cleaned up by the framework. They will be unloaded when a client closes the browser window with the Silverlight client.

Carlos Oliveira
Posted on 1/23/2009 3:21:52 AM

Hey Vlad,

Sorry I didn't specify, but I'm not using Silverlight in this case, I am using it for a .NET Web Application using the RadGrid. The incoming XML will not be in a consistent format or shape and what I really want is something that lets me take that xml and bind it to the grid dynamically.

I originally thought LINQ would be perfect for this but essentially you still have to strongly type your new anonymous object as I couldn't find any way to build that object dynamically based on the incoming values. Your solution allows me to do that by using the IDictionary, but this is an Enterprise application and the memory issues will definitely come back to bite me.

I'm now looking at loading the XML into a DataSet and working from that, but I really wanted a slightly cleaner and less handbuilt solution, but I guess we can't have it all!

Thanks for your help,

Carlos

Vladimir Bodurov
Posted on 1/23/2009 9:37:23 AM

This is solution for client-side Silverlight not server-side asp.net. For asp.net solution go to my article http://www.simple-talk.com/dotnet/.net-framework/dynamically-generating--typed-objects-in-.net/#alternatives and use WindowsApplicationDataBinding.cs

Javier Minsky
Posted on 2/3/2009 12:15:50 PM

Many thanks for your code!!!! Good job.
May I ask you why do you use the Regex? For example, I wanna put in a key a "," or a space. I added comments in the if verification and it worked perfect, but there is a recommentadation to not add spaces or commas in a key?

Thanks in advance.
Regards, Javier.

Vladimir Bodurov
Posted on 2/3/2009 12:37:50 PM

Keys will become object properties. So any limitations you have for a .NET variable name will apply here as well. There should be no real reason though for you not to accept them as the client will not see those keys. Use underscores.

Mahesh
Posted on 2/16/2009 10:30:17 PM

Hi,
I'm new in this technology, Can you please tell me how to bind multiple rows with same grid I'm able to bind my grid as per the above example with single row now I want to bind it with the multiple rows with same grid.

Waiting for your reply.

Thanks
Mahesh

Vladimir Bodurov
Posted on 2/17/2009 9:30:37 AM

Hi Mahesh,

Sorry but I am not sure I understand your question. I will also suggest you ask it at http://forums.asp.net/15.aspx as there you will get opinions from many different experts.

James
Posted on 3/1/2009 1:17:24 PM

Hi Vlad,

I have a unique situation where I may need to access the dynamic type from javascript to read/change some field values. Everything I've read says that when I create an object dynamically, then they are automatically marked as scriptable. I am able to pass the object to javascript but cannot access any of the properties.

Any ideas?

Thanks!

James
Posted on 3/1/2009 1:50:31 PM

Hi Vlad,

Never mind my question, I found it...see below for anyone who's interested:

        private static void CreateProperty( 
                        TypeBuilder tb, string propertyName, Type propertyType) 
        { 
            FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, 
                                                        propertyType, 
                                                        FieldAttributes.Private); 
 
 
            PropertyBuilder propertyBuilder = 
                tb.DefineProperty( 
                    propertyName, PropertyAttributes.HasDefault, propertyType, null); 
 
            // add the three lines below to mark this member as a 'ScriptableMember' 
            ConstructorInfo cTor = typeof(ScriptableMemberAttribute).GetConstructor(new Type[] { }); 
            CustomAttributeBuilder cBldr = new CustomAttributeBuilder(cTor, new object[] { }); 
            propertyBuilder.SetCustomAttribute(cBldr); 
 
            MethodBuilder getPropMthdBldr = 
Raag
Posted on 3/13/2009 5:38:39 AM

Good Work....i have struggled lot to achive this....thought create a Custom class from webservice/wcf but while getting the same in Silverlight it was failing...your code was superb...

Many thanks for your code!!!! Good job.....i have checked your code....

var t = this.TheGrid.SelectedItem.GetType();
var s = (string)t.GetProperty("Name").GetValue(this.TheGrid.SelectedItem, null);
HtmlPage.Window.Alert("Name:" + s);

one of the best thing is to move this to Datasource class....

Is it possible to retrive entire row's info in a collection instead of accessing property by property....

It's possible to loop through the property ang get the list...but without looping entire collection is it possible

Brian
Posted on 3/13/2009 10:54:21 AM

Using EventBuilder... How could I modify your class to add an "ItemUpdated" that is fired on the set_ method of a property that can pass the index to an event in the Parent Enumerable... so that, in short.. the generated Enumerable has an event that sends the index of the item that just had the set_ method invoked... I want to attach that event within a parent generic class and maintain a list of "ModifiedItems" ?

Vladimir Bodurov
Posted on 3/13/2009 3:27:53 PM

Hi Brian,

I am afraid I won't have time to write it for you but here is a tip http://beaucrawford.net/post/Dynamically-creating-an-aspect-using-TypeBuilder.aspx

Try writing it!

Andrus
Posted on 3/18/2009 11:20:48 AM

Best solution may be to add INotifyPropertyChanged implementation to generated properties.

How to force property setters to call OnPropertyChanged() method on base class ?

Andrus

Raag
Posted on 3/20/2009 5:11:01 AM

I'm getting no Data for specifice field.

If there is Null in first record any column,then all the rows of that column will have Null....if there is a value in second or third row it wont show it will show as Null...how to fix this....i want set String.empty if there is null for a record but offcourse want to show value for that column if the value is there.

Help me

Vladimir Bodurov
Posted on 3/20/2009 8:24:01 AM

Change this:

foreach (IDictionary currentDict in list) 
{ 
    hasData = true; 
    firstDict = currentDict; 
    break; 
} 

to this

foreach (IDictionary currentDict in list) 
{ 
    hasData = true; 
    firstDict = currentDict; 
    if(RecordsCanBeUsedAsModel(firstDict)) break; 
} 

where RecordsCanBeUsedAsModel is a method that checks if you have all records OK

Sunny
Posted on 3/26/2009 2:59:36 PM

Hi Vlad,

I want to use above solution. But in my case the object has to be returned by a WCF service. Can you please send me the code changes to the above solution that would work for me.

Any help would be greatly appreciated.

Help!!

Vladimir Bodurov
Posted on 3/26/2009 5:31:55 PM

I do not recommend using this for non-Silverlight projects. For how to decorate an object for WCF you can see my post at http://blog.bodurov.com/Create-a-WCF-client-for-asmx-web-service-without-using-web-proxy

Colin Eberhardt
Posted on 4/22/2009 6:52:12 AM

Hi Valdimir,

This is excellent work and I am sure I will use it in the future. I have created an alternative implementation via ValueConverters and some other trickery, resulting in an editable grid:

http://www.scottlogic.co.uk/blog/wpf/2009/04/binding-a-silverlight-datagrid-to-dynamic-data-part-2-editable-data-and-inotifypropertychanged/

Just thought you might be interested!

Regards, Colin E.

Vladimir Bodurov
Posted on 4/22/2009 8:02:57 AM

Thanks Colin,

This is interesting solution!

Jay
Posted on 4/25/2009 2:49:06 PM

I have a question though. On SL 3.0 datagrid, I added a PropertyGroupDescription. Specified a PropertyName. Grouping works fine, but the description in the grouping shows up as <PropertyName>: (<number of items> items)
Hence if I am grouping by _countryId which has 3 _stateId's which has 10 _cityId's the 2 level grouping shows
_countryId: 10 (3 items)
_stateId: 111 (10 items)
<then list of cities>

How can I change the description of the propertygroupdescription?

Antonis
Posted on 5/7/2009 5:18:44 AM

Hi,

i tried to make it work through WCF using the ServiceClient you posted (the one without using a web proxy) and i couldn't make it work. The problem is that i cannot serialize a [code]IEnumerable or IEnumerable<IDictionary>[/code] type in order to pass it to the other side.

Any suggestions?! Great work by the way!

Vladimir Bodurov
Posted on 5/7/2009 8:29:58 AM

It won't work with the exact code posted here because you have to make the object to implement a decorated interface and also to decorate the dynamic object with attributes. I may blog about this when I find some time in the future, but this code need changes to make it work with WCF, this is a solution for binding a grid not for talking to service

Iulia
Posted on 5/8/2009 6:59:35 AM

Hello,

Your code is awesome and I was looking for something exactly like this. The only problem I have is that I'm getting stopped here: "Each key of IDictionary must be alphanumeric and start with character". I am trying to retrieve some data from the AdventureWorks sample OLAP cube through MDX query and ADOMD and display the data using a web service in a DataGrid in my Silverlight application. Now the data has lots of numbers and characters like $ and . ($23.45).

Can you please help me with this or is there any way I can overcome this?
Thank you!

Vladimir Bodurov
Posted on 5/8/2009 8:45:16 AM

Well, those are values not keys so they will not cause any problem. The column names will become the keys I guess. If you have space there simply replace it with underscore.

Iulia
Posted on 5/8/2009 10:55:01 AM

Thank you for the prompt response.

In the string with all the data retrieved from the cube I have as the first row a list of headers for the columns and the rest goes in order as the line values for those columns. So I have:

List<string> lines = rez.Split(new char[] { '\n' }).ToList();
List<string> headers = lines[0].Split(new char[] { '\t' }).ToList();

I'm calling GenerateData like this:
this.theDataGrid.ItemsSource = GenerateData(headers,lines).ToDataSource();

where GenerateData looks like this:

public IEnumerable<IDictionary> GenerateData(List<string> headers, List<string> lines)
{

foreach (string line in lines)
{
List<string> values = line.Split(new char[] { '\t' }).ToList();
var dict = new Dictionary<string, string>();

for (int j = 0; j < headers.Count; j++)
{
dict.Add(headers[j], values[j]);
}

yield return dict;
}

}

With this code and the DataSourceCreator.cs from your tutorial I get the "Each key of IDictionary must be alphanumeric and start with character." error.

I may not have understood what you said in your answer. Please give me more details. If it help to view the whole picture of my project please take a look here: http://rapidshare.com/files/230676886/SilverlightApplication2.rar.html
I hope the link works!

Iulia
Posted on 5/8/2009 11:04:03 AM

Here are some screenshots of the data as shows in a messagebox so you can see what I'm trying to display in the datagrid:
http://www.flickr.com/photos/50373695@N00/3512487319/

CY 2001 .. and so on are the column headers.
If you know any other way of displaying such data at runtime please let me know.

Vladimir Bodurov
Posted on 5/8/2009 12:27:21 PM

Just inside your GenerateData method change this line

dict.Add(headers[j], values[j]);  

to

dict.Add(EnsureName(headers[j]), values[j]); 

and add function

public static string EnsureName(string name){ 
   return name.Replace(" ","_").Replace("-","_").Replace(",","_"); 
} 

or replace whatever character you get there

Iulia
Posted on 5/8/2009 2:29:35 PM

I've done what you wrote but I'm still getting the same error.

Vladimir Bodurov
Posted on 5/8/2009 4:30:43 PM

Then debug it and see what character that is contained in the string passed to EnsureName function is not in the range [A-Za-z0-9] and when you find it add it to the replace calls. May be you have ' or " or , or something like that. Debug it and see.

Iulia
Posted on 5/9/2009 12:07:26 AM

Ok... the keys seem ok as values but everything stops at the first matching of the regular expression:
if (PropertNameRegex.IsMatch(Convert.ToString(pair.Key), 0))

pair.Key = "CY_2001"
PropertNameRegex=^[A-Za-z]+[A-Za-z1-9_]*

and it says "Convert.ToString(pair.Key) This expression causes side effects and will not be evaluated"

Any suggestions?

Vladimir Bodurov
Posted on 5/10/2009 12:12:23 AM

Yes sorry there was a typo in the regular expression that I missed so the regular expression should actually be
new Regex(@"^[A-Za-z]+[A-Za-z0-9_]*$", RegexOptions.Singleline);

I corrected the code on the site

Iulia
Posted on 5/10/2009 2:04:56 AM

Using the new DataSourceCreator.cs you modified I am still getting the same error: "Each key of IDictionary must be alphanumeric and start with character" and after debugging I see that it stops at the Convert.ToString(pair.Key)part saying "This expression causes side effects and will not be evaluated".
The data in firstDict is
System.Collections.Generic.KeyValuePair<string,string>
+ [0] {[CY_2001, Alabama]}
+ [1] {[CY_2002, ]}
+ [2] {[CY_2003, $8.64]}
+ [3] {[CY_2004, $11.51]}
+ [4] {[CY_2006, $19.52]}

I think the keys as they look here should pass the
if (PropertNameRegex.IsMatch(Convert.ToString(pair.Key), 0))
part and not enter the else branch and stop.

Please take a look at the code and help me with this. I need to figure out and solve this problem in order to display my data properly in the datagrid.

Thanks a lot for the patience!

Iulia
Posted on 5/10/2009 4:14:44 AM

I have changed the key values replacing 0 with o and something like this and I managed to step over that error but the result looks like this:
http://www.flickr.com/photos/50373695@N00/3517513835/sizes/o/in/photostream/

Any suggestions?

Vladimir Bodurov
Posted on 5/10/2009 9:48:16 AM

Well the real problem is that those characters cannot be turned into property names so make you replacement function as

public static string EnsureName(string name){  
   return (new StringBuilder()).Replace(" ","_").Replace("-","_").Replace(",","").Replace(".","_").Replace("$","USD").ToString();  
} 

Then assign the header names separately as shown in the first code snippet of this blog.

Sandi
Posted on 5/12/2009 10:05:02 AM

Brilliant bit of code! Solved a problem for me that I have been struggling with for a week. Thank You!

Vitaly Laskarzhesky
Posted on 5/27/2009 4:09:21 AM

Hi Vladimir,

Thank you for your ideas about dynamic DTO generation.

Using them, I created Silverlight DataSet which is mirroring ADO.Net DataSet at client side and allows two-way binding to Silverlight controls with regular binding syntax. In other words, it is possible to bind ADO.Net DataSet to Silverlight controls.

Could you look at it when you have a time and provide your valuable opinion about it? Here is a link: http://silverlightdataset.codeplex.com/

Thanks again.

Vladimir Bodurov
Posted on 5/27/2009 9:10:26 AM

Thanks Vitaly,

Interesting idea and very well structured documentation!

Vitaly Laskarzhevsky
Posted on 5/29/2009 6:18:02 AM

Hi Vladimir,

I need o modify code for dynamic type generation for implementing INotificationChange interface. Unfortunately my knowledge in emit namespace is limited and there are no examples on Internet of how to emit events. Could you provide a short example of it if that is not hard for you?

Thank you

Vladimir Bodurov
Posted on 5/29/2009 7:00:16 PM

You probably mean INotifyPropertyChanged. It is here:

http://www.bodurov.com/files/DataSourceCreator_INotifyPropertyChanged.zip

Mark
Posted on 6/11/2009 10:11:48 AM

Vladimir - thank you for the excellent post and your patience in responding to all these replies. Good stuff. Though - I'm considering an alternate approach using DLR only because I'd rather learn Python than IL ;)

Vinod Sa
Posted on 6/16/2009 5:16:25 AM

Hi,
Nice post.BUT when I tried doing same thing using VB.NET I am getting following exception:
Unable to cast object of type 'System.Collections.Generic.List`1[TempType51385085]' to type 'System.Object[]'.

Can be reproducible using Vb.NET code

Regards,
Vinod Sa.

Martien de Jong
Posted on 7/10/2009 9:48:19 AM

Fantastic! Great to see there are still some great minds on the internet ;)

CraigD
Posted on 7/12/2009 5:31:31 AM

This is an awesome bit of code - exactly what I needed to make a 'generic' SQL result display

http://conceptdev.blogspot.com/2009/07/sql-in-silverlight.html

Because I wanted it to fit my model exactly, I had to make a few minor changes eg.

Dictionary<string,string>
http://conceptdevelopment.net/Silverlight2/SharpSql02/DataSourceCreator.cs.html

THANK YOU!

Sep
Posted on 7/16/2009 1:16:36 AM

Exactly what I needed. Thank you very much.

Kirk Cessac
Posted on 7/24/2009 2:52:07 PM

This is fantastic! I was contemplating writing this mechanism when I came across your article. Absolutely fantastic!

Fallon Massey
Posted on 8/12/2009 5:55:08 PM

Vladimir, what does generation of INotifyPropertyChanged add to the solution?

Does that allow two way binding to the collection?

Thanks.

Vladimir Bodurov
Posted on 8/12/2009 6:02:16 PM

That allows you to have binding in both directions DataSource - UI and UI - DataSource.

For more info you can read this tutorial: http://silverlight.net/learn/tutorials/databinding.aspx

Fallon Massey
Posted on 8/12/2009 10:43:57 PM

Thanks Vladimir, that's what I suspected.

Fantastic work!

Fallon Massey
Posted on 8/12/2009 10:48:01 PM

One more thing.

Performance would be improved if the type created could somehow be cached, so the creation would only occur once.

That way, you would only have to use reflection to insert new values into the object.

I don't mind doing the work if you can tell me how to proceed.

Vladimir Bodurov
Posted on 8/12/2009 11:59:01 PM

Type caching is already implemented, the types are stored in the Dictionary<string, Type> _typeBySigniture collection

slyi
Posted on 8/16/2009 4:10:52 PM

I love your class,with sl3 styles that can set more than once it works beauifully http://cid-289eaf995528b9fd.skydrive.live.com/self.aspx/Public/MDic.zip

Vitaly Laskarzhevsky
Posted on 8/21/2009 7:58:23 AM

Hi Vladimir,

Silverlight DataSet (http://silverlightdataset.codeplex.com) supports binding to DataGrid with AutoGenerateColumns="True" now.

Again, thank you very much for your help with code generation samples you provided.

Alfa
Posted on 11/9/2009 3:25:34 AM

Hi Vladimir,

I am very new to these. Could you please provide me the any example where your following object is used for INotifyPropertyChanged.
http://www.bodurov.com/files/DataSourceCreator_INotifyPropertyChanged.zip

I am able to populate grid but does not know how to implement INotifyPropertyChanged and ObservableCollection.

rdg
Alfa

Vladimir Bodurov
Posted on 11/9/2009 4:26:20 AM

If you want to use ObservableCollection you would have to replace this row

var listType = typeof(List<>).MakeGenericType(new[] { objectType }); 

in the same very file you are pointing to with this row

var listType = typeof(ObservableCollection<>).MakeGenericType(new[] { objectType }); 
Grayson Mitchell
Posted on 11/11/2009 3:18:51 PM

Ausome code!

One little thing is not working for me, I create my columns (which have different header names to the binding names), and then I run ToDataSource. Where there is a difference between the binding name and the header name an additional column gets created (with the binding name). In an earlier post you seem to have said you fixed this problem?

I am using the standard datagrid that comes with silverlight 3 (weirdly if I use the agDataGrid instead there is no problem - other than some agDataGrid issues)

Vladimir Bodurov
Posted on 11/11/2009 3:58:09 PM

Use AutoGenerateColumns="False" as in the example above

Grayson Mitchell
Posted on 11/12/2009 6:47:11 PM

Ahh, solved - thanks a heap!

Theoretically I now should be able to use this approach with the DataForm. I havn't managed to get it working yet passing in a single record as opposed to the complete recordset). The interesting question is could you implement validation from metadata on the DataForm in anyway.

Vitaly Laskarzhevsky
Posted on 12/4/2009 9:24:10 AM

Hi Vladimir,

Is it possible to emit annotation attributes (System.ComponentModel.DataAnnotations) using Emit namespace, for example [Required()] attribute?

Could you give me code example if you know how?

Thank you
Vitaly

Vladimir Bodurov
Posted on 12/4/2009 11:41:25 AM
ConstructorInfo attrConstructorInfo = typeof(Required).GetConstructor(new Type[]{}); 
var customAttributeBuilder = new CustomAttributeBuilder(attrConstructorInfo, new object[]{}); 
propertyBuilder.SetCustomAttribute(customAttributeBuilder); 
Michael C
Posted on 1/7/2010 10:04:19 PM

Hi Vladimir,

Thanks so much for sharing this code - this was exactly what I was looking for!! :)

-Michael.

Markten
Posted on 1/22/2010 11:28:20 AM

Hi Vladimir,

Great Code by the way, help me a lot!

Working fine whit the displaying part into a grid, but I not able to access the properties for the selected Item
into the SelectionChanged event from my grid, I'm using VB with Silverlight 3 not C# , when tracing the code, I've got this
Dim t = MyGrid.SelectedItem.GetType() it give me the base type System.RunTimeType instead of the real type TemTypeXXXXX

seems to me that this line of code behave differently into C# that giving me the right type..the TempTYpeXXXXX, when I run your GridTestUI in C#

so when running this line of code
Dim s As String = t.GetProperty("StockNumber").GetValue(MyGrid.SelectedItem, vbNull)

I have this exception error that said
Application code cannot access System.Type.GetProperty(System.String) using Reflection.

can you help? Thanks!

Markten
Posted on 1/22/2010 1:00:00 PM

Hi Vlad,

Never mind my question, I found it...see below for anyone who's interested:

in VB what you need too do is to get the type from the type and recast it like this

Dim t As Type = If(TypeOf MyGrid.SelectedItem.GetType Is Type, DirectCast(MyGrid.SelectedItem.GetType, Type), MyGrid.SelectedItem.[GetType]())

Dim s As String = ""
Dim i As Integer

For i = 0 To MyGrid.SelectedItems.Count - 1
s = t.GetProperty("StockNumber").GetValue(MyGrid.SelectedItems(i), Nothing)
MessageBox.Show(s)
Next

Fallon Massey
Posted on 1/23/2010 3:37:26 PM

Hi Vladimir, I was wondering if using the .Net 4.0 dynamic Expando Object would be an enhancement to this code.

Actually the question is if .Net 4.0 would allow you to rewrite the dynamic object creation portion of this code?

Thanks!

Ritesh
Posted on 1/25/2010 12:04:06 AM

Hi Vladimir,
awesome code. something which solves a real tough problem in a flash.
I am able to bind the grid, using the code, but i wish a bit more things. Can you please also provide code for also inserting and updating the data. may be if u can provide the code for firing of "OnPropertyChanged" then it will also be helpful. thnx in advance

Moritz
Posted on 1/27/2010 5:28:56 AM

Hi Vladimir, thanks a lot for all your code and knowledge!

I can display an Dictionary just fine, using your method. However, I'm stuck at trying to modify the values using the WPF DataGrid. The values are changed within your temporary dynamic classes, but the change does not get propagated to the original Dictionary. Do I miss something here? (I'm using your code with the INotifyPropertyChanged additions. Shouldn't this version propagate the changes to the underlying Dictionary?)

Thanks for all the information on your blog!
Moritz

Moritz
Posted on 1/27/2010 5:30:03 AM

BTW: Commenting on your blog doesn't seem to work using Chrome. (Just so you know.)

Vladimir Bodurov
Posted on 1/27/2010 5:43:25 AM

The original dictionary is just a model. You can cast IEnumerable result to IList if you want to do something with it.

P.S. I added this comment with Chrome

Moritz
Posted on 1/27/2010 5:59:04 AM

Thanks for the reply!

I know that I can cast it to List if I want to do something with it in code. What I want to do is:
Modify the values in the model (= in the original Dictionary) by clicking and typing in the WPF DataGrid (= using pure Databinding, no C# code). (The DataGrid is bound to a ViewModel which exposes the Dictionary which is really a property of some data model object).

PS: Trying to submit this using Chrome...

Agustin Berbegall
Posted on 2/4/2010 9:23:02 AM

Hi Vladimir
First of all, great work and very good solution!

I'm using your code for binding data(originally DataSet in Asp.net and serialized as Json array in Silverlight) to a Silverlight combobox ItemSource.
I can get access to the each property value with this method

        public static object GetSelectedValue(this ComboBox pobjSender, String pstrPropertyName) 
        { 
            object objValue = null; 
            if ((pobjSender != null) && (pobjSender.SelectedItem != null)) 
            { 
                var type = pobjSender.SelectedItem.GetType(); 
                PropertyInfo objPropInfo = type.GetProperty(pstrPropertyName); 
 
                if (objPropInfo != null) { 
                    objValue = objPropInfo.GetValue(pobjSender.SelectedItem, null); 
                } 
            } 
 
            return objValue; 
        } 
 

that works fine, but Is there any way for retrieve the whole object binding directly(A.k.a. the DataContext)

Thanks a lot in advance

Boogie
Posted on 2/7/2010 1:00:25 PM

Hi Vinod Sa,
It seems that, aside from us, got this error.
Im having trouble with that part of code, too.

Vikas
Posted on 3/15/2010 5:25:08 AM

Hi Vladimir,

Great post. I have been struggling with this situation for quite some time until I found your post. Thanks a lot for sharing your great work.

Hats off to you!

Sandy
Posted on 3/21/2010 9:00:53 PM

Hi Vladimir Bodurov,

I have atleast following four things for CellTemplate
#1: Item Title
#2: Item Image
#3: Item Description
#4: Duration

Values for these four items is different for different cells, which are determined at run time. Moreover, the height of every cell is determined by the DURATION at run time.

Could you please guide me how to use RadGridView/Data Grid for my requirement?
Thanks in advance!!

Regards,
Sandy

Sandy
Posted on 3/23/2010 12:10:31 PM

Hi Vladimir Bodurov,

Psuedo code is as given below

public class cItemDetails
{

public string itemTitle { get; set; }
public string displayImageSrc{ get; set; }
public string itemDescription{ get; set; }
public int itemDuration{ get; set; }
}

List<cItemDetails> lstItemDetailsColumnWise; //for simplicity, code for filling lstItemDetailsColumnWise with objects of cItemDetails is removed

//XAML Code
<DataTemplate x:Key="myCellTemplate">
<TextBlock Text="{Binding itemTitle}" />
<Image Source="{Binding displayImageSrc}"/>
<TextBlock Text="{Binding itemDescription}"/>
<TextBlock Text="{Binding itemDuration}"/>
</DataTemplate>

I am stuck because of following limitations
#1: lstItemDetailsColumnWise is the list of data source objects for cells in a column but I am not able to find the way how to set ItemSource at column level in RadGridView?
GridViewColumn grdViewColumn = new GridViewColumn();
grdViewColumn.CellTemplate = (DataTemplate)Resources["myCellTemplate"];
GridViewLength dataGridLen = new GridViewLength(300.0);
grdViewColumn.Width = dataGridLen;
grdViewColumn.ItemSource = ????? (Not there?)

#2: To avoid limitation#1, if I create GridViewCell and set DataContext as follows
GridViewColumn grdViewColumn = new GridViewColumn();
GridViewCell grdViewCell = new GridViewCell();
grdViewCell.ContentTemplate = (DataTemplate)Resources["myCellTemplate"];
grdViewCell.DataContext = lstItemDetailsColumnWise[i]; // i varies from 0 to n (lstItemDetailsColumnWise.Count - 1)
grdViewColumn.Add(grdViewCell) - ????? (Not there?)
Here I don't know how to set grdViewCell in RadGridViewDataColumn?

#3: If I create double list of cItems and set it as ItemSource of RadGridView as follows
List<List<cItems>> dblListItemDetails //Code of adding contents in this list is not shown here for simplicity sake
RadGridView.ItemSource = dblListItemDetails;
Result: RadGridView is not able to populate the data from double list

Here, you will find that I am stuck with all the options where in first two options worked fine when I was using Silverlight Grid control.
I prefer to use Option#2, control at cell level because cell height depends on ItemDuration, which means cell height will not be same across any Row in RadGridView. I don't know if RadGridView can have row with cells of different heights, may be CellSpan will come to rescue.

Why is it so difficult to use RadGridView control when you have requirement of binding every cell with multiple properties whose values come from separate business object for each cell?
I want to have control at cell level for setting contents of the cell and also setting the height of every cell.
Is it possible with RadGridView?
Please help!!

Thanks,
Sandy

Vladimir Bodurov
Posted on 3/23/2010 12:57:08 PM

Sorry Sandy, I don't know RadGridView and I am affraid I won't have the time to analyse your problem. Better ask on asp.net forums.

Yogendra
Posted on 6/16/2011 10:02:50 AM

Hi,
My problem is, when i use the code posted here, i could not able to bind the datagrid properly with the result as i am using WCF service. Please suggestest the required changes for using the code with WCF.
I will highly greatful to you.
Thanks a lot in Advance

David J
Posted on 12/4/2014 3:08:48 PM

Hey Vlad,
What is the Licensing on this?
thanks!

Vladimir Bodurov
Posted on 12/4/2014 10:40:05 PM

The most open one: MIT License, I will add note about that

Florent Bénetière
Posted on 7/28/2017 6:23:18 AM

Hi Vladimir

Thanks a lot for your class.

I've applied minor changes to it to :

- Accept the "0" in property name

Before :
Return New Regex("^[A-Za-z]+[A-Za-z1-9_]*$", RegexOptions.Singleline)

After :
Return New Regex("^[A-Za-z]+[A-Za-z0-9_]*$", RegexOptions.Singleline)

- Accept a value when it's Nothing

Before :

For Each pair In firstDict
If (PropertNameRegex.IsMatch(Convert.ToString(pair.Key), 0)) Then
CreateProperty(tb, Convert.ToString(pair.Key), pair.Value.GetType)
Else

After :

For Each pair In firstDict
If (PropertNameRegex.IsMatch(Convert.ToString(pair.Key), 0)) Then
Dim unType As Type = Nothing

If IsNothing(pair.Value) Then
unType = "Par défaut".GetType 'On est obligé de forcer un type qui ne soit pas Nothing au cas où pair.value = Nothing
Else
unType = pair.Value.GetType
End If
CreateProperty(tb, Convert.ToString(pair.Key), unType)

Vladimir Bodurov
Posted on 7/28/2017 8:25:26 AM

Thanks Florent, I will add your changes.

Jason
Posted on 10/21/2017 2:27:02 AM

Hi Vladimir,
Thanks for your class!It's powerful!

I had implemented dynamic two-way binding at SL's pplication, but when my want to edit grid source, I found i cannot simple convert to IDictionary data type. Could you give me some idea to convert to original data type for the binding data source?
Thanks for all!

Vladimir Bodurov
Posted on 10/21/2017 10:26:57 AM
Commenting temporarily disabled