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

发布于 2024-10-01 05:02:12 字数 1321 浏览 5 评论 0原文

好吧,这是一个艰难的问题。

简介:我的想法是将我编写的实例化 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;}
}

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

因此,这导致了三个问题:

  1. 这是否可以通过某种方式重写 QueryBuilder 类的 Type 的 GetProperties() 来完成,如果可以,最好如何完成?

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

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

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

Ok, this is a tough one.

Introduction: My idea is to attach an instanciated QueryBuilder class which I wrote, to a PropertyGrid. The QueryBuilder class now contains a couple of fields, which are hardcoded like in the example below. Thus allowing a user to specify, which fields should be used in a query in what way (sorted, grouped, and so on). After the user having specified all the settings to these properties (by code or via the PropertyGrid GUI), the QueryBuilder is able to produce a query. Everything is working fine like that. Pseudo code:

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

Challenge: Now instead of hardcoding each field as public properties in the QueryBuilder class, I was wondering how I could use i.e. a List<string> containing all my fields to "populate" my instanciated QueryBuilder with these properties.

So this leads to three questions:

  1. Could this be accomplished by somehow overriding GetProperties() of the Type of the QueryBuilder class, and if yes, how is it best done?

  2. How can I then iterate through all of these at runtime generated QBField properties and instanciate them? Idea: PropertyDescriptors and Activators?

  3. How can I iterate through all of these properties to read the values of each QBField object? The problem I ran in was, that when reading the Properties of QBField with reflection and trying getValue(obj, null), of course the first parameter needed is an object, which I do not know since I have lots of these QBField objects. Perhaps putting all my QBFields into a List<QBField> and iterating through it? Would that work in this example?

I'm just a bit lost but I feel that I'm very close to the solution. Therefore any help or just pointers in the right direction are most greatly appreciated!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

_失温 2024-10-08 05:02:12

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

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

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

这是一个基本属性将所有内容公开为字符串的包;显然,当你扩展这个(不同的属性类型、更改通知等)时,它很快就会变得更加复杂。另请注意,此 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<MetaProp> properties = new List<MetaProp>();
    public List<MetaProp> Properties { get { return properties; } }
    private readonly Dictionary<string, object> values = new Dictionary<string, object>();

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

}

PropertyGrid can be influenced via TypeConverter, ICustomTypeDescriptor and/or TypeDescriptionProvider. Of these, TypeConverter is the simplest, by overriding GetProperties (and mark it as supported).

In any event, you will also need to write a PropertyDescriptor implementation that knows how to take a field and an object, and get/set the value, i.e.

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

Here's a basic property bag that exposes everything as string; obviously as you extend this (different property types, change-notification, etc) it gets more complex very quickly. Note also that this TypeConverter approach only works for PropertyGrid; for DataGridView etc you'll need either ICustomTypeDescriptor or TypeDescriptionProvider. For collections you'll need ITypedList. And there are about 20 other interfaces around the edges for specific scenarios. But you get the point ;p The key thing is that our PropertyDescriptor acts as the translation between your actual model (the dictionary in my case), and the model you expose to TypeDescriptor (the fake properties per key).

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<MetaProp> properties = new List<MetaProp>();
    public List<MetaProp> Properties { get { return properties; } }
    private readonly Dictionary<string, object> values = new Dictionary<string, object>();

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

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文