在运行时生成的属性(PropertyGrid.SelectedObject)

好的,这是一个艰难的。

简介:我的想法是将我编写的instanciated QueryBuilder类附加到PropertyGrid。 QueryBuilder类现在包含几个字段,这些字段是硬编码的,如下例所示。 从而允许用户指定应以何种方式在查询中使用哪些字段(排序,分组等)。 在用户为这些属性指定了所有设置后(通过代码或通过PropertyGrid GUI),QueryBuilder能够生成查询。 一切都很好。 伪代码:

class QueryBuilder { public QBField name {get; set;} public QBField prename {get; set;} public QBField zip {get; set;} // ... public void QueryBuilder() { name = new QBField(); prename = new QBField(); // ... } public getQuery() { // logic to build the query } } class QBField { public bool shown {get; set;} public bool sortby {get; set;} public bool groupby {get; set;} } 

挑战:现在我不想将每个字段硬编码为QueryBuilder类中的公共属性,而是想知道如何使用包含所有字段的List来“填充”我的instanciated QueryBuilder这些属性。

所以这导致了三个问题:

  1. 这可以通过以某种方式覆盖QueryBuilder类的Type的GetProperties()来实现,如果是的话,它是如何做得最好的?

  2. 然后,我如何在运行时生成的QBField属性迭代所有这些属性并实例化它们? 想法:PropertyDescriptors和Activators?

  3. 如何迭代所有这些属性来读取每个QBField对象的值? 我遇到的问题是,当用reflection读取QBField的属性并尝试getValue(obj,null)时,当然需要的第一个参数是一个对象,我不知道,因为我有很多这些QBField对象。 也许将我的所有QBField放入List并迭代它? 这会在这个例子中起作用吗?

我只是有点迷失,但我觉得我非常接近解决方案。 因此,非常感谢任何帮助或指向正确方向的指针!

可以通过TypeConverterICustomTypeDescriptor和/或TypeDescriptionProvider来影响PropertyGrid 。 其中, TypeConverter是最简单的,通过重写GetProperties (并将其标记为支持)。

无论如何,您还需要编写一个PropertyDescriptor实现,该实现知道如何获取字段和对象,并获取/设置值,即

 public override void SetValue(object component, object value) { ((YourType)component)[fieldNameSetInConstructor] = value; } 

这是一个基本的属性包,将所有内容暴露为string ; 显然,当您扩展它(不同的属性类型,更改通知等)时,它会变得更加复杂。 另请注意,此TypeConverter方法仅适用于PropertyGrid ; 对于DataGridView等,您需要ICustomTypeDescriptorTypeDescriptionProvider 。 对于集合,您需要ITypedList 。 对于特定场景,边缘周围还有大约20个其他接口。 但是你得到了重点; p关键是我们的PropertyDescriptor充当你的实际模型(在我的例子中是字典)和你暴露给TypeDescriptor的模型(每个键的假属性)之间的转换。

 using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Windows.Forms; static class Program { [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); var bag = new BasicPropertyBag { Properties = { new MetaProp("Name", typeof(string)), new MetaProp("Description", typeof(string)), new MetaProp("DateOfBirth", typeof(DateTime) , new CategoryAttribute("Personal"), new DisplayNameAttribute("Date Of Birth")) } }; bag["Name"] = "foo"; bag["DateOfBirth"] = DateTime.Today; Application.Run(new Form { Controls = { new PropertyGrid { Dock = DockStyle.Fill, SelectedObject = bag } } }); } } public class MetaProp { public MetaProp(string name, Type type, params Attribute[] attributes) { this.Name = name; this.Type = type; if (attributes != null) { Attributes = new Attribute[attributes.Length]; attributes.CopyTo(Attributes, 0); } } public string Name { get; private set; } public Type Type { get; private set; } public Attribute[] Attributes { get; private set; } } [TypeConverter(typeof(BasicPropertyBagConverter))] class BasicPropertyBag { private readonly List properties = new List(); public List Properties { get { return properties; } } private readonly Dictionary values = new Dictionary(); public object this[string key] { get { object value; return values.TryGetValue(key, out value) ? value : null; } set { if (value == null) values.Remove(key); else values[key] = value; } } class BasicPropertyBagConverter : ExpandableObjectConverter { public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) { PropertyDescriptor[] metaProps = (from prop in ((BasicPropertyBag)value).Properties select new PropertyBagDescriptor(prop.Name, prop.Type, prop.Attributes)).ToArray(); return new PropertyDescriptorCollection(metaProps); } } class PropertyBagDescriptor : PropertyDescriptor { private readonly Type type; public PropertyBagDescriptor(string name, Type type, Attribute[] attributes) : base(name, attributes) { this.type = type; } public override Type PropertyType { get { return type; } } public override object GetValue(object component) { return ((BasicPropertyBag)component)[Name]; } public override void SetValue(object component, object value) { ((BasicPropertyBag)component)[Name] = (string)value; } public override bool ShouldSerializeValue(object component) { return GetValue(component) != null; } public override bool CanResetValue(object component) { return true; } public override void ResetValue(object component) { SetValue(component, null); } public override bool IsReadOnly { get { return false; } } public override Type ComponentType { get { return typeof(BasicPropertyBag); } } } }