user13051425
user13051425

Reputation:

How to create in C# WPF a DataGrid dynamic columns binded to an observable collection of dynamic properties types at runtime

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

Answers (1)

user12805184
user12805184

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

Related Questions