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