2014-01-14 2 views
2

Я отправляю это с таким большим количеством объяснений, как могу, так как мне было трудно найти общую информацию по этому вопросу и хотел поделиться своими выводами с сообществом SO.Связывание данных со сложными/вложенными объектами (C#)

Связывание данных с набором сложных объектов в C# обычно не позволяет считывать данные из вложенных объектов внутри класса. Примером этого является член экземпляра class A - объект class B. Если вам нужны свойства из внутреннего объекта (B в этом случае), когда источник коллекции/привязки используется в качестве источника данных, вам не повезло без дополнительной работы или доступа к исходному классу для модификации.

Вопрос: «Как вы можете использовать данные из внутренних классов при привязке данных к объекту пользовательского интерфейса без доступа к изменению исходного класса?»

ответ

5

Данные во внутренних классах могут быть абсолютно использованы в сопоставлении привязки данных, но не по умолчанию. Лучший способ справиться с этим - установить PropertyDescriptors и TypeDescriptors. Способ, который я собираюсь объяснить ниже, находится в общей версии , но позволит обеспечить привязку данных к внутренним объектам без необходимости изменения исходных классов или расширений для реализации интерфейсов. Это здорово, если вы не являетесь автором классов, которые используете, или если вы используете классы, сопоставленные ORM.

Есть 4 части реализации этого решения:

  1. Расширение PropertyDescriptor класса для доступа к внутренним объектам
  2. CustomTypeDescriptor реализации
  3. TypeDescriptonProvider реализации
  4. Крепежных новообразованному провайдер к типу, который нам нужен для доступа к данным.

ЧАСТЬ 1 - расширение PropertyDescriptor класса:

Для того, чтобы получить доступ внутренних компонентов нам нужны, чтобы получить их PropertyDescriptor с, что, по существу, метаданными, используемые для доступа к общим свойствам класса. Это можно сделать, добавив PropertyDescriptor для доступа к дочерним свойствам. Кроме того, это, когда вы реализуете способы чтения и записи на эти объекты или устанавливаете их только для чтения (как и я).

class SubPropertyDescriptor : PropertyDescriptor 
{ 
    private PropertyDescriptor _parent; 
    private PropertyDescriptor _child; 

    public SubPropertyDescriptor(PropertyDescriptor parent, PropertyDescriptor child, string propertyDescriptorName) 
     : base(propertyDescriptorName, null) 
    { 
     _child = child; 
     _parent = parent; 
    } 
    //in this example I have made this read-only, but you can set this to false to allow two-way data-binding 
    public override bool IsReadOnly{ get { return true; } } 
    public override void ResetValue(object component) { } 
    public override bool CanResetValue(object component){ return false; } 
    public override bool ShouldSerializeValue(object component){ return true;} 
    public override Type ComponentType{ get { return _parent.ComponentType; } } 
    public override Type PropertyType{ get { return _child.PropertyType; } } 
    //this is how the value for the property 'described' is accessed 
    public override object GetValue(object component) 
    { 
     return _child.GetValue(_parent.GetValue(component)); 
    } 
    /*My example has the read-only value set to true, so a full implementation of the SetValue() function is not necessary. 
    However, for two-day binding this must be fully implemented similar to the above method. */ 
    public override void SetValue(object component, object value) 
    { 
     //READ ONLY 
     /*Example: _child.SetValue(_parent.GetValue(component), value); 
      Add any event fires or other additional functions here to handle a data update*/ 
    } 
} 

Часть 2 - Реализация CustomTypeDescriptor:

CustomTypeDesciptor является то, что создает тег метаданных, чтобы связывание данных из внутренних объектов. По существу, мы будем создавать «дескрипторные строки», которые ссылаются на свойства Type для внутренних объектов, а затем добавляют их к родительскому объекту. Формат, используемый для внутренних объектов, будет следующим "className_property", где classname - это Type внутреннего объекта.

class MyClassTypeDescriptors : CustomTypeDescriptor 
{ 
    Type typeProp; 

    public MyClassTypeDescriptors(ICustomTypeDescriptor parent, Type type) 
     : base(parent) 
    { 
     typeProp = type; 
    } 
    //This method will add the additional properties to the object. 
    //It helps to think of the various PropertyDescriptors are columns in a database table 
    public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) 
    { 
     PropertyDescriptorCollection cols = base.GetProperties(attributes); 
     string propName = ""; //empty string to be populated later 
     //find the matching property in the type being called. 
     foreach (PropertyDescriptor col in cols) 
     { 
      if (col.PropertyType.Name == typeProp.Name) 
       propName = col.Name; 
     } 
     PropertyDescriptor pd = cols[propName]; 
     PropertyDescriptorCollection children = pd.GetChildProperties(); //expand the child object 

     PropertyDescriptor[] propDescripts = new PropertyDescriptor[cols.Count + children.Count]; 
     int count = cols.Count; //start adding at the last index of the array 
     cols.CopyTo(propDescripts, 0); 
     //creation of the 'descriptor strings' 
     foreach (PropertyDescriptor cpd in children) 
     { 
      propDescripts[count] = new SubPropertyDescriptor(pd, cpd, pd.Name + "_" + cpd.Name); 
      count++; 
     } 

     PropertyDescriptorCollection newCols = new PropertyDescriptorCollection(propDescripts); 
     return newCols; 
    } 
} 

На данный момент мы имеем теперь наши «дескрипторов строки» для настройки привязок к объектам innre.Внутренние свойства MyClass можно назвать как "MyOtherClass_Property1" и другие свойства могут назвать, как обычно, с их именами переменных "Property1"

Часть 3 - Реализация TypeDescriptonProvider:

Это последний заказ кусок, который нам потребуется создавать. A TypeDescriptionProvider - это часть, которую объект, связанный с данными, будет использовать для определения свойств объекта и является тем, что используется для фактического вызова нашего класса CustomTypeDescriptor всякий раз, когда требуются дескрипторы. Это также один класс, который использует generics, но на самом деле не является общим классом, так как мы должны подключить его к нашему внешнему объекту (так называемый тип данных используемой коллекции).

class MyClassTypeDescProvider<T> : TypeDescriptionProvider 
{ 
    private ICustomTypeDescriptor td; 

    public DigiRecordBindingTypeDescProvider() 
     : this(TypeDescriptor.GetProvider(typeof(MyClass))) 
    { } 

    public MyClassTypeDescProvider(TypeDescriptionProvider parent) 
     : base(parent) 
    { } 

    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) 
    { 
     if (td == null) 
     { 
      td = base.GetTypeDescriptor(objectType, instance); 
      td = new MyClassTypeDescriptors(td, typeof(T)); 
     } 
     return td; 
    } 
} 

Общий класс «T» используется для обозначения Type внутреннего свойства объекта, что нам нужно будет сделать ссылку на наш родительский объект. Вы увидите, как это работает на следующем шаге.

Части 4 - Присоединение нашего поставщика к родительскому типу:

Теперь, когда мы создали инфраструктуру для доступа к данным, хранящимся во внутренних свойствах мы должны сообщить системе, чтобы использовать нашу настроенную поставщику при поиске наших TypeDescriptors. Это делается с помощью статического метода:

TypeDescriptor.AddProvider(provider,type) 

Это должно быть сделано для каждого внутреннего Type, где требуется доступ к внутренним свойствам, как. Добавление провайдера должно быть выполнено ПЕРЕД привязкой данных к связанному объекту, например, при установке свойства DataSource объекта UI, например.

IQueryable<MyClass> myData = PopulateCollectionWithData(); 
TypeDescriptor.AddProvider(new MyClassTypeDescProvider<MyOtherClass>(), typeof(MyClass)); 
TypeDescriptor.AddProvider(new MyClassTypeDescProvider<MyThirdClass>(), typeof(MyClass)); 
DataGridView1.DataSource = myData; //don't bind directly to a collection if you are doing two-way binding. Use a BindingSource instead! 

Наконец, если по какой-то причине вам нужно удалить этот провайдер и вернуться к значениям по умолчанию вы можете выполнить ту же самую операцию в обратном направлении:

TypeDescriptor.RemoveProvider(new MyClassTypeDescProvider<MyOtherClass>(), typeof(MyClass)); 
TypeDescriptor.RemoveProvider(new MyClassTypeDescProvider<MyThirdClass>(), typeof(MyClass)); 

См TypeDescriptor Class - MSDN или The MSDN blog that put me on the right track для получения дополнительной информации. Кроме того, во время моих исследований по этому вопросу я наткнулся на вопрос this SO, который побудил меня опубликовать полное объяснение, поскольку на самом деле было просто просить часть 4 этого ответа. Я надеюсь, что это поможет кому-то, поэтому им не нужно копаться в библиотеке System.ComponentModel столько, сколько я без необходимости!

Смежные вопросы