Reputation:
I'm trying to create in C# WPF a DataGrid with dynamic columns binded to an observable collection of dynamic properties types created at runtime.
This is my code :
View WPF
<DataGrid
ItemsSource="{Binding MyCollectionVM, Mode=OneWay}"
AutoGenerateColumns="True">
</DataGrid>
Then in my ViewModel :
public class MyStatiClass
{
public int ID { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
// Main View Model, using MVVMLight library
public class MainViewModel : ViewModelBase
{
private ObservableCollection<MyStatiClass> _myCollectionVM = new ObservableCollection<MyStatiClass>();
public ObservableCollection<MyStatiClass> MyCollectionVM
{
get => _myCollectionVM;
set => Set(nameof(MyCollectionVM), ref _myCollectionVM, value);
}
public MainViewModel()
{
MyCollectionVM.Add(new MyStatiClass() { ID = 1, Name = "Name1", Address = "15 Hollywood Street"});
}
}
MyStatiClass
contains as an example three properties but I want to generate as many properties as wanted dynamically at runtime. These properties will be generated elsewhere to fit some business needs.
I tried several methods like using List<dynamic>
, Dictionary<>
, ExpandoObject
, ... , but each time, the DataGrid
which uses reflection is displaying the first level properties passed in the type MyStatiClass
and not the real properties of MyStatiClass
that I wanted.
My question is, How can I do this ?
Thank you for your help. Regards
Upvotes: 2
Views: 1518
Reputation:
I faced the same issue by the past and found this solution based on the Excellent article from Kailash Chandra Behera.
The secret relies on using System.Reflection.Emit
which provides classes that allow a compiler or tool to emit metadata and Microsoft intermediate language (MSIL) and optionally generate a PE file on disk. The primary clients of these classes are script engines and compilers.
For the curious and passionate, you can go ahead : System.Reflection.Emit Namespace and Introduction to Creating Dynamic Types with Reflection.Emit
Solution :
List<dynamic>
, Dictionary<>
, ExpandoObject
cannot works because, reflection will be stalled on the first level hierarchy of your instance class MyStatiClass
.
The only solution I found is to create dynamically the complete MyStatiClass
at runtime including as instance namespace, class name, properties names, attributes, etc. .
This is the ViewModel code fitting your question :
public class MainViewModel : ViewModelBase
{
private ObservableCollectionEx<dynamic> _myCollectionVM = new ObservableCollectionEx<dynamic>();
public ObservableCollectionEx<dynamic> MyCollectionVM
{
get => _myCollectionVM;
set => Set(nameof(MyCollectionVM), ref _myCollectionVM, value);
}
public MainViewModel()
{
MyClassBuilder myClassBuilder = new MyClassBuilder("DynamicClass");
var myDynamicClass = myClassBuilder.CreateObject(new string[3] { "ID", "Name", "Address" }, new Type[3] { typeof(int), typeof(string), typeof(string) });
MyCollectionVM.Add(myDynamicClass);
// You can either change properties value like the following
myDynamicClass.ID = 1;
myDynamicClass.Name = "John";
myDynamicClass.Address = "Hollywood boulevard";
}
}
Remark : Compilation checks and Intellisense won't work will dynamic types, so take care of your properties syntax, otherwise you'd get an exception raised at runtime.
Then the Dynamic Class Factory Builder which will build the full class at runtime :
/// <summary>
/// Dynamic Class Factory Builder
/// </summary>
public class MyClassBuilder
{
AssemblyName asemblyName;
public MyClassBuilder(string ClassName)
{
asemblyName = new AssemblyName(ClassName);
}
public dynamic CreateObject(string[] PropertyNames, Type[] Types)
{
if (PropertyNames.Length != Types.Length)
{
throw new Exception("The number of property names should match their corresopnding types number");
}
TypeBuilder DynamicClass = CreateClass();
CreateConstructor(DynamicClass);
for (int ind = 0; ind < PropertyNames.Count(); ind++)
CreateProperty(DynamicClass, PropertyNames[ind], Types[ind]);
Type type = DynamicClass.CreateType();
return Activator.CreateInstance(type);
}
private TypeBuilder CreateClass()
{
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(asemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
TypeBuilder typeBuilder = moduleBuilder.DefineType(asemblyName.FullName
, TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout
, null);
return typeBuilder;
}
private void CreateConstructor(TypeBuilder typeBuilder)
{
typeBuilder.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
}
private void CreateProperty(TypeBuilder typeBuilder, string propertyName, Type propertyType)
{
FieldBuilder fieldBuilder = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
MethodBuilder getPropMthdBldr = typeBuilder.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 = typeBuilder.DefineMethod("set_" + propertyName,
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
null, new[] { propertyType });
ILGenerator setIl = setPropMthdBldr.GetILGenerator();
Label modifyProperty = setIl.DefineLabel();
Label exitSet = setIl.DefineLabel();
setIl.MarkLabel(modifyProperty);
setIl.Emit(OpCodes.Ldarg_0);
setIl.Emit(OpCodes.Ldarg_1);
setIl.Emit(OpCodes.Stfld, fieldBuilder);
setIl.Emit(OpCodes.Nop);
setIl.MarkLabel(exitSet);
setIl.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getPropMthdBldr);
propertyBuilder.SetSetMethod(setPropMthdBldr);
}
}
Enjoy it.
Upvotes: 1