c# - 如何在 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
例如包含三个属性,但我想在运行时动态生成尽可能多的属性。这些属性将在其他地方生成以满足某些业务需求。
我尝试了几种方法,例如 using List<dynamic>
, Dictionary<>
, ExpandoObject
, ... ,但是每次DataGrid
使用反射的方法都是显示在类型中传递的第一级属性,而不是我想要MyStatiClass
的真实属性。MyStatiClass
我的问题是,我该怎么做?
谢谢您的帮助。问候
解决方案
我过去遇到过同样的问题,并根据Kailash Chandra Behera的优秀文章找到了这个解决方案。
秘密依赖于使用System.Reflection.Emit
which 提供的类允许编译器或工具发出元数据和 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);
}
}
好好享受。
推荐阅读
- java - Telegram,Java:上传文件以在 sendAnimation 中发送
- javascript - 如何修复javascript中的settimeout内存泄漏问题
- sql - 使用来自子查询的多个值进行更新
- c++ - 通过函数释放内存 - 这是对的吗?
- sql - 如何使用 distinct 和 sum 编写 SQL 报告
- python - 将多索引数据帧写入 csv 而不更改其格式
- php - PHP Wordpress:在 Safari 中 print_r() 输出到哪里?
- regex - RegEx 用于匹配逗号前的单词,但有例外
- c# - 将 Xamarin.Forms 从 2.xx 升级到 3.xx 时出错
- sql - 在 OVER 子句中使用 ORDER BY