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