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

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

问题描述

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

这是我的代码:

查看WPF

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

然后在我的 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包含三个属性作为示例,但我想在运行时动态生成所需数量的属性.这些属性将在其他位置生成以满足某些业务需求.

我尝试了几种方法,例如使用List<dynamic>Dictionary<>ExpandoObject,...,但是每次使用反射的DataGrid都会显示以MyStatiClass类型传递的第一级属性,并且不是我想要的MyStatiClass的真实属性.

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

感谢您的帮助. 问候

解决方案

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

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

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

解决方案:

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

这是适合您问题的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";
    }
}

备注:动态类型将无法进行编译检查和Intellisense,因此请注意属性语法,否则在运行时会引发异常.

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

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

享受吧.

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天全站免登陆