如何在 C# WPF 中创建在运行时绑定到动态属性类型的可观察集合的 DataGrid 动态列 [英] How to create in C# WPF a DataGrid dynamic columns binded to an observable collection of dynamic properties types at runtime
问题描述
我正在尝试在 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
作为示例包含三个属性,但我想在运行时动态生成尽可能多的属性.这些属性将在别处生成以满足某些业务需求.
我尝试了几种方法,例如使用 List
、Dictionary<>
、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屋!