Как создать в C# WPF динамические c столбцы DataGrid, связанные с наблюдаемой коллекцией типов свойств динамических c во время выполнения - PullRequest
1 голос
/ 27 апреля 2020

Я пытаюсь создать в C# WPF DataGrid с динамическими c столбцами, привязанными к наблюдаемой коллекции динамических c типов свойств, созданных во время выполнения.

Это мой код:

Просмотр 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, которые я хотел.

Мой вопрос: как я могу это сделать?

Спасибо Вы за вашу помощь. Привет

1 Ответ

1 голос
/ 27 апреля 2020

В прошлом я сталкивался с той же проблемой и нашел это решение на основе статьи Отлично от Кайла sh Чандра Бехера .

Секрет основан на использовании System.Reflection.Emit, который предоставляет классы, которые позволяют компилятору или инструменту генерировать метаданные и промежуточный язык Microsoft (MSIL) и дополнительно генерировать PE-файл на диске. Основными клиентами этих классов являются скриптовые движки и компиляторы.

Для любопытных и увлеченных вы можете go впереди: System.Reflection.Emit Namespace и Введение в создание Dynami c Типы с Reflection.Emit

Решение :

List<dynamic>, Dictionary<>, ExpandoObject не может работать, потому что отражение будет остановился на иерархии первого уровня вашего экземпляра класса MyStatiClass. Единственное решение, которое я нашел, - это динамическое создание полного MyStatiClass во время выполнения, включая пространство имен экземпляра, имя класса, имена свойств, атрибуты и т. Д. c. .

Это код 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 не будут работать c типов, так что позаботьтесь о синтаксисе свойств, иначе вы получите исключение во время выполнения.

Затем Dynami c Class Factory Builder, который создаст полный класс во время выполнения:

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

Наслаждайтесь.

...