如何在 C# WPF 中创建在运行时绑定到动态属性类型的可观察集合的 DataGrid 动态列 [英] How to create in C# WPF a DataGrid dynamic columns binded to an observable collection of dynamic properties types at runtime

查看:20
本文介绍了如何在 C# WPF 中创建在运行时绑定到动态属性类型的可观察集合的 DataGrid 动态列的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在 C# WPF 中创建一个 DataGrid,其中动态列绑定到在运行时创建的动态属性类型的可观察集合.

这是我的代码:

查看 WPF

</DataGrid>

然后在我的 ViewModel 中:

公共类 MyStatiClass{公共 int ID { 获取;放;}公共字符串名称 { 获取;放;}公共字符串地址{获取;放;}}//主视图模型,使用 MVVMLight 库公共类 MainViewModel : ViewModelBase{私有 ObservableCollection_myCollectionVM = new ObservableCollection();公共 ObservableCollection我的收藏虚拟机{得到 =>_myCollectionVM;设置 =>Set(nameof(MyCollectionVM), ref _myCollectionVM, value);}公共主视图模型(){MyCollectionVM.Add(new MyStatiClass() { ID = 1, Name = "Name1", Address = "15 Hollywood Street"});}}

MyStatiClass 作为示例包含三个属性,但我想在运行时动态生成尽可能多的属性.这些属性将在别处生成以满足某些业务需求.

我尝试了几种方法,例如使用 ListDictionary<>ExpandoObject、...,但每次,使用反射的 DataGrid 显示在类型 MyStatiClass 中传递的第一级属性,而不是我想要的 MyStatiClass 的真实属性.

我的问题是,我该怎么做?

感谢您的帮助.问候

解决方案

我过去遇到过同样的问题,并根据 Kailash Chandra Behera.>

秘密依赖于使用 System.Reflection.Emit,它提供的类允许编译器或工具发出元数据和 Microsoft 中间语言 (MSIL),并可选择在磁盘上生成 PE 文件.这些类的主要客户是脚本引擎和编译器.

对于好奇和热情的人,您可以继续:System.Reflection.Emit 命名空间介绍使用 Reflection.Emit 创建动态类型

解决方案:

List, Dictionary<> , ExpandoObject 无法工作,因为反射将在您的第一级层次结构上停滞实例类 MyStatiClass.我找到的唯一解决方案是在运行时动态创建完整的 MyStatiClass,包括实例命名空间、类名、属性名、属性等.

这是适合您问题的 ViewModel 代码:

公共类 MainViewModel : ViewModelBase{私有 ObservableCollectionEx_myCollectionVM = new ObservableCollectionEx();公共 ObservableCollectionEx我的收藏虚拟机{得到 =>_myCollectionVM;设置 =>Set(nameof(MyCollectionVM), ref _myCollectionVM, value);}公共主视图模型(){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);//您可以更改属性值,如下所示myDynamicClass.ID = 1;myDynamicClass.Name = "约翰";myDynamicClass.Address = "好莱坞大道";}}

备注:编译检查和智能感知对动态类型不起作用,因此请注意您的属性语法,否则您会在运行时引发异常.

然后是动态类工厂生成器,它将在运行时构建完整的类:

////<摘要>///动态类工厂生成器///</总结>公共类 MyClassBuilder{装配名称 装配名称;公共 MyClassBuilder(字符串类名){asemblyName = new AssemblyName(ClassName);}公共动态 CreateObject(string[] PropertyNames, Type[] Types){if (PropertyNames.Length != Types.Length){throw new Exception("属性名的数量应该与其对应的类型数相匹配");}TypeBuilder DynamicClass = CreateClass();CreateConstructor(DynamicClass);for (int ind = 0; ind < PropertyNames.Count(); ind++)CreateProperty(DynamicClass, PropertyNames[ind], Types[ind]);类型类型 = DynamicClass.CreateType();返回 Activator.CreateInstance(type);}私有类型构建器 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, 空值);返回类型生成器;}私有无效 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();标签修改属性 = setIl.DefineLabel();标签 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);}}

享受吧.

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

解决方案

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.

这篇关于如何在 C# WPF 中创建在运行时绑定到动态属性类型的可观察集合的 DataGrid 动态列的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆