简体   繁体   English

如何在 C# WPF 中创建在运行时绑定到动态属性类型的可观察集合的 DataGrid 动态列

[英]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.我正在尝试在 C# WPF 中创建一个 DataGrid,其中动态列绑定到在运行时创建的动态属性类型的可观察集合。

This is my code:这是我的代码:

View WPF查看 WPF

<DataGrid
    ItemsSource="{Binding MyCollectionVM, Mode=OneWay}"
    AutoGenerateColumns="True">
</DataGrid>

Then in my ViewModel :然后在我的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. MyStatiClass包含三个属性作为示例,但我想在运行时动态生成尽可能多的属性。 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.我尝试了几种方法,例如使用List<dynamic>Dictionary<>ExpandoObject , ... ,但是每次使用反射的DataGrid都显示在MyStatiClass类型中传递的第一级属性,而不是MyStatiClass我的真实属性通缉。

My question is, How can I do this?我的问题是,我该怎么做?

Thank you for your help.谢谢您的帮助。 Regards问候

I faced the same issue by the past and found this solution based on the Excellent article from Kailash Chandra Behera .我过去遇到过同样的问题,并根据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.秘密依赖于使用System.Reflection.Emit ,它提供的类允许编译器或工具发出元数据和 Microsoft 中间语言 (MSIL),并可选择在磁盘上生成 PE 文件。 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对于好奇和热情的人,可以先看 go: System.Reflection.Emit 命名空间使用 Reflection.Emit 创建动态类型的介绍

Solution :解决方案

List<dynamic> , Dictionary<> , ExpandoObject cannot works because, reflection will be stalled on the first level hierarchy of your instance class MyStatiClass . List<dynamic>Dictionary<>ExpandoObject无法工作,因为反射将在您的实例 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. .我发现的唯一解决方案是在运行时动态创建完整的MyStatiClass ,包括实例命名空间、class 名称、属性名称、属性等。

This is the ViewModel code fitting your question:这是适合您问题的 ViewModel 代码:

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.备注:编译检查和 Intellisense 对动态类型不起作用,因此请注意您的属性语法,否则您会在运行时引发异常。

Then the Dynamic Class Factory Builder which will build the full class at runtime:然后是动态 Class 工厂构建器,它将在运行时构建完整的 class:

/// <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.好好享受。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM