Поиск обходного пути невозможности привязки элемента управления DataGridView к иерархическим (ОО) данным - PullRequest
5 голосов
/ 18 ноября 2010

Казалось бы, элемент управления DataGridView может связываться только с плоскими источниками данных (все свойства являются основными типами). Мои данные иерархические. Например:

    interface INestedObj
{
    string Prop3 { get; }
}

interface IParentObj
{
    public string Prop1 { get; }
    public string Prop2 { get; }
    public INestedObj NestedObj { get; }
}

Учитывая это, как связать объект, реализующий IParentObj? В конце концов вы сталкиваетесь с необходимостью сделать что-то вроде этого:

grid.Columns["prop1Col"].DataPropertyName = "Prop1";
grid.Columns["prop2Col"].DataPropertyName = "Prop2";

grid.Columns["prop3Col"].DataPropertyName = "How to display Prop3?";

grid.Columns["prop3Col"].DataPropertyName = "NestedObj.Prop3"; // does not work

Я ищу советы и / или обходные пути.

ТИА

Ответы [ 4 ]

7 голосов
/ 18 ноября 2010

Вы можете выставлять свойства из INestedObj для привязки, но решение это очень грязное. какие свойства существуют на объектах, к которым они привязаны. С помощью TypeDescriptionProvider и CustomTypeDescriptor вы можете переопределить поведение по умолчанию и, следовательно, добавить / скрыть свойства - в этом случае скрыть свойство NestedObj и заменить его всеми свойствами вложенного типа.

Техника, которую я собираюсь показать, имеет 2 (большие) предостережения:

  1. Поскольку вы работаете с интерфейсами (а не с конкретными классами), вы должны добавить дескриптор пользовательского типа во время выполнения.
  2. Пользовательский дескриптор типа должен иметь возможность создавать конкретный экземпляр IParentObj, поэтому он должен знать один такой класс, который имеет конструктор по умолчанию.

(прошу прощения за длинный код)

Во-первых, вам нужно обернуть PropertyDescriptor из вложенного типа, чтобы к нему можно было обращаться из родительского типа:

public class InnerPropertyDescriptor : PropertyDescriptor {
    private PropertyDescriptor innerDescriptor;

    public InnerPropertyDescriptor(PropertyDescriptor owner, 
        PropertyDescriptor innerDescriptor, Attribute[] attributes)
        : base(owner.Name + "." + innerDescriptor.Name, attributes) {
        this.innerDescriptor = innerDescriptor;
    }
    public override bool CanResetValue(object component) {
        return innerDescriptor.CanResetValue(((IParentObj)component).NestedObj);
    }
    public override Type ComponentType {
        get { return innerDescriptor.ComponentType; }
    }
    public override object GetValue(object component) {
        return innerDescriptor.GetValue(((IParentObj)component).NestedObj);
    }
    public override bool IsReadOnly {
        get { return innerDescriptor.IsReadOnly; }
    }
    public override Type PropertyType {
        get { return innerDescriptor.PropertyType; }
    }
    public override void ResetValue(object component) {
        innerDescriptor.ResetValue(((IParentObj)component).NestedObj);
    }
    public override void SetValue(object component, object value) {
        innerDescriptor.SetValue(((IParentObj)component).NestedObj, value);
    }
    public override bool ShouldSerializeValue(object component) {
        return innerDescriptor.ShouldSerializeValue(
            ((IParentObj)component).NestedObj
        );
    }
}

Затем вам нужно написать собственный дескриптор типа, который предоставляет свойства из вложенного типа:

public class ParentObjDescriptor : CustomTypeDescriptor {
    public override PropertyDescriptorCollection GetProperties(
        Attribute[] attributes) {
        PropertyDescriptorCollection properties
            = new PropertyDescriptorCollection(null);

        foreach (PropertyDescriptor outer in TypeDescriptor.GetProperties(
            new ParentObj() /* concrete implementation of IParentObj */, 
            attributes, true)) {
            if (outer.PropertyType == typeof(INestedObj)) {
                foreach (PropertyDescriptor inner in TypeDescriptor.GetProperties(
                    typeof(INestedObj))) {
                    properties.Add(new InnerPropertyDescriptor(outer, 
                        inner, attributes));
                }
            }
            else {
                properties.Add(outer);
            }
        }

        return properties;
    }
}

... и тогда вам нужен способ раскрытия дескриптора сверху:

public class ParentObjDescriptionProvider : TypeDescriptionProvider {
    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, 
        object instance) {
        return new ParentObjDescriptor();
    }
}

Наконец, во время выполнения (перед привязкой к DataGridView) необходимо связать поставщика описания типа с интерфейсом IParentObj. Вы не можете сделать это во время компиляции, потому что TypeDescriptionProviderAttribute нельзя разместить на интерфейсах ...

TypeDescriptor.AddProvider(new ParentObjDescriptionProvider(), typeof(IParentObj));

Я проверил это, связав DataGridView с IParentObj[] и, низко и вот, он создает столбцы для Prop1, Prop2 и NestedObj.Prop3.

Вы должны спросить себя, хотя ... это действительно стоит всех этих усилий?

3 голосов
/ 18 ноября 2010

Вот простое решение, которое пришло ко мне в конце долгого дня.

Я использовал запрос и проекцию Linq для создания анонимного типа, который отображает правильную информацию в DataGridView.

var query = from pt in parentObjCollection
  select new {Prop1=pt.Prop1, Prop2=pt.Prop2, NestedObj.Prop3=pt.NestedObj.Prop3};

Мне пришлось указать правильное значение (NestedObj.Prop3) для свойства DataPropertyName, чтобы получить значение, отображаемое в сетке.

Когда у меня будет больше времени, я попытаюсь реализовать решение Брэдли.

2 голосов
/ 18 ноября 2010

Возможно, вы могли бы добавить несвязанный столбец для «NestedObj.Prop3» и вручную обработать его значение. Чтобы заполнить столбец, обработайте событие CellFormatting объекта DataGridView, получите DataBoundItem из текущей строки и получите из него Prop3. Чтобы обновить источник данных, обработайте событие CellValidated, чтобы обновить DataBoundItem.

Может быть более подходящие события, чем те, что я упомянул, но вы поняли.

0 голосов
/ 16 мая 2011

Самый простой способ, который я нашел, - создать свойство Self.См. Это решение: Привязка данных столбца комбинированного списка к сетке данных для каждой строки (не для всего столбца)

...