How to Bind Silverlight DataGrid From IEnumerable of IDictionary by Transforming Each Dictionary Key

 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 transformIDictionary (for example Dictionary orHashtable, 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 eachIDictionary insideIEnumerable 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 eachIEnumerable 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;
            }
        }
    }

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

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值