Binding data grid at run time

Note: This post is originally of Vladimir Bodurov, MCSD.Just wanted to share in CSharpCorner for better visibility.

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;

using System.Windows.Browser;

 

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);

 

            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));

                    }

                    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)

        {

            return value == null ? typeof(object) : value.GetType();

        }

 

        private static string GetTypeSigniture(IDictionary firstDict)

        {

            StringBuilder sb = new StringBuilder();

            foreach (DictionaryEntry pair in firstDict)

            {

                sb.AppendFormat("_{0}_{1}", pair.Key, GetValueType(pair.Value));

            }

            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));

                        property.SetValue(

                            row,

                            Convert.ChangeType(

                                    currentDict[pair.Key],

                                    property.PropertyType,

                                    null),

                            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)

        {

            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);

        }
    }

 

}


Similar Articles