Свойства, созданные во время выполнения (PropertyGrid.SelectedObject) - PullRequest
3 голосов
/ 04 ноября 2010

Хорошо, это сложный вопрос.

Введение: Моя идея заключается в том, чтобы присоединить созданный мной класс QueryBuilder к PropertyGrid. Класс QueryBuilder теперь содержит пару полей, которые жестко запрограммированы, как в примере ниже. Таким образом, пользователь может указать, какие поля следует использовать в запросе, каким образом (сортировать, группировать и т. Д.). После того, как пользователь указал все параметры этих свойств (с помощью кода или через графический интерфейс PropertyGrid), 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<string>, содержащий все мои поля, чтобы «заполнить» мой экземплярный QueryBuilder этими свойства.

Итак, это приводит к трем вопросам:

  1. Может ли это быть достигнуто путем переопределения GetProperties () класса Type класса QueryBuilder, и если да, то как это лучше всего сделать?

  2. Как я могу затем пройти через все это во время выполнения сгенерированных свойств QBField и создать их экземпляр? Идея: PropertyDescriptors и Activators?

  3. Как я могу перебрать все эти свойства, чтобы прочитать значения каждого объекта QBField? Проблема, с которой я столкнулся, заключалась в том, что при чтении свойств QBField с отражением и попытке getValue (obj, null), конечно, первый необходимый параметр - это объект, которого я не знаю, так как у меня много этих объектов QBField. Возможно, положить все мои QBFields в List<QBField> и перебрать его? Будет ли это работать в этом примере?

Я немного растерялся, но чувствую, что очень близок к решению. Поэтому любая помощь или просто указатели в правильном направлении очень ценятся!

1 Ответ

5 голосов
/ 04 ноября 2010
На

PropertyGrid можно воздействовать через TypeConverter, ICustomTypeDescriptor и / или TypeDescriptionProvider. Из них TypeConverter является самым простым, переопределяя GetProperties (и пометьте его как поддерживаемый).

В любом случае вам также нужно написать реализацию PropertyDescriptor, которая знает, как взять поле и объект, и получить / установить значение, т.е.

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

Вот сумка свойств basic , которая выставляет все как string; очевидно, когда вы расширяете это (различные типы свойств, уведомление об изменении и т. д.), оно очень быстро усложняется. Также обратите внимание, что этот подход TypeConverter работает только для PropertyGrid; для DataGridView и т. д. вам потребуется либо ICustomTypeDescriptor, либо TypeDescriptionProvider. Для коллекций вам понадобится 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); } }
    }

}
...