[C# 2.0] Complex data-binding со свойствами вложенных объект
От: _FRED_ Черногория
Дата: 29.05.06 18:02
Оценка: 6 (2)
Всвязи с наплывом сообщений по теме, решился опубликовать одно решение. Я не считаю его удачным, но зато оно простое в использовании как две копейки, да и показывает некоторые возможности работы с компонентной моделью.

Начнём с примера использования:
Файл с описанием классов бизнес-объектов
using System;
using System.ComponentModel;
using System.Diagnostics;

using MyCompany.ComponentModel;

namespace DefinePropertyModel
{
  public class DataItem1
  {
    private string text;

    public DataItem1()
    {
    }

    public DataItem1(string text)
    {
      this.text = text;
    }

    public string Text
    {
      get { return text ?? String.Empty; }
      set { text = value; }
    }
  }

  [TypeDescriptionProvider(typeof(DefinePropertyTypeDescriptionProvider<DataItem2>))] // Подключаем свой поставщик свойств
  [DefineProperty("Item1.Text")] // Определяем свойства
  [DefineProperty("Item2.Text", "Text2")]
  public class DataItem2
  {
    private readonly DataItem1 item1;
    private readonly DataItem1 item2;

    public DataItem2()
    {
      item1 = new DataItem1();
      item2 = new DataItem1();
    }

    public DataItem2(string text1, string text2)
    {
      item1 = new DataItem1(text1);
      item2 = new DataItem1(text2);
    }

    [Browsable(false)]
    public DataItem1 Item1
    {
      [DebuggerStepThrough]
      get { return item1; }
    }

    [Browsable(false)]
    public DataItem1 Item2
    {
      [DebuggerStepThrough]
      get { return item2; }
    }
  }
}


Использование, например, такое:
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Forms;

using DefinePropertyModel;
using MyCompany.ComponentModel;

namespace WindowsApplication1
{
  static class Program
  {
    [STAThread]
    static void Main()
    {
      DataItem2 item = new DataItem2("Text #1", "text #2");
      IList list = new List<DataItem2>();
      foreach(PropertyDescriptor propertyDescriptor in ListBindingHelper.GetListItemProperties(list))
      {
        Debug.Print("Name: {0}, Type: {1}, Value: {2}", propertyDescriptor.Name, propertyDescriptor.PropertyType, propertyDescriptor.GetValue(item));
      }//for
    }
  }
}


Описанные с помощью DefinePropertyAttribute свойства понимаются визуальными редактрами (объект можно добавить как DataSource к проекту, подключать к BindingSource и дальше: к гридам, другим элементам управления).

Исходники — в ответе.
... << RSDN@Home 1.2.0 alpha rev. 650>>
Now playing: «Тихо в лесу…»
Help will always be given at Hogwarts to those who ask for it.
Re: [C# 2.0] Complex data-binding со свойствами вложенных об
От: _FRED_ Черногория
Дата: 29.05.06 18:10
Оценка: 4 (1)
Здравствуйте, _FRED_, Вы писали:

_FR>Исходники — в ответе.


// DefinePropertyAttribute.cs
using System;
using System.ComponentModel;
using System.Diagnostics;

namespace MyCompany.ComponentModel
{
  [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, Inherited = true)]
  [DebuggerDisplay("Expression={Expression}, PropertyName={PropertyName}")]
  public class DefinePropertyAttribute : Attribute
  {
    #region Fields

    public const char PropertySeparator = '.';

    private readonly string expression;
    private string propertyName;

    #endregion Fields

    #region Constructors\Finalizer

    public DefinePropertyAttribute(string expression)
    {
      if(String.IsNullOrEmpty(expression))
      {
        throw new ArgumentNullException("expression");
      }//if

      this.expression = expression;
    }

    public DefinePropertyAttribute(string expression, string propertyName) : this(expression)
    {
      this.propertyName = propertyName;
    }

    #endregion Constructors\Finalizer

    #region Properties

    public string Expression
    {
      [DebuggerStepThrough]
      get { return expression; }
    }

    public string PropertyName
    {
      [DebuggerStepThrough]
      get { return propertyName ?? String.Empty; }
      set { propertyName = value; }
    }

    internal string SafePropertyName
    {
      [DebuggerStepThrough]
      get { return !String.IsNullOrEmpty(PropertyName) ? PropertyName : Expression; }
    }

    #endregion Properties
  }
}

// DefinePropertyTypeDescriptionProvider`1.cs
using System;
using System.ComponentModel;
using System.Diagnostics;

namespace MyCompany.ComponentModel
{
  public class DefinePropertyTypeDescriptionProvider<T> : TypeDescriptionProvider
  {
    #region Fields

    private static readonly TypeDescriptionProvider innerProvider = TypeDescriptor.GetProvider(typeof(T));

    #endregion Fields

    #region Constructors\Finalizer

    public DefinePropertyTypeDescriptionProvider()
    {
    }

    #endregion Constructors\Finalizer

    #region Properties

    private static TypeDescriptionProvider InnerProvider
    {
      [DebuggerStepThrough]
      get { return innerProvider; }
    }

    #endregion Properties

    #region Overrides

    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
    {
      return new DefinePropertyTypeDescriptor(objectType, instance, InnerProvider.GetTypeDescriptor(objectType, instance));
    }

    #endregion Overrides
  }
}


// DefinePropertyTypeDescriptor.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;

namespace MyCompany.ComponentModel
{
  internal partial class DefinePropertyTypeDescriptor : CustomTypeDescriptor
  {
    #region Fields

    private static readonly Attribute[] DefinedPropertyAttributes = new Attribute[] {
      BrowsableAttribute.Yes,
    };

    private readonly Type componentType;
    private readonly object instance;
    private PropertyDescriptorCollection properties;

    #endregion Fields

    #region Constructors\Finalizer

    public DefinePropertyTypeDescriptor(Type componentType, object instance, ICustomTypeDescriptor parentDescriptor)
      : base(parentDescriptor)
    {
      if(componentType == null)
      {
        throw new ArgumentNullException("componentType");
      }//if

      this.componentType = componentType;
      this.instance = instance;
    }

    #endregion Constructor\Finalizer

    #region Properties

    public Type ComponentType
    {
      [DebuggerStepThrough]
      get { return this.componentType; }
    }

    public object Instance
    {
      [DebuggerStepThrough]
      get { return instance; }
    }

    #endregion Properties

    #region Methods

    protected PropertyDescriptor CreateDefinePropertyDescriptor(DefinePropertyAttribute attribute)
    {
      return CreateDefinePropertyDescriptor(attribute, DefinedPropertyAttributes);
    }

    protected PropertyDescriptor CreateDefinePropertyDescriptor(DefinePropertyAttribute attribute, Attribute[] attributes)
    {
      return CreateDefinePropertyDescriptor(ComponentType, attribute, attributes);
    }

    protected virtual PropertyDescriptor CreateDefinePropertyDescriptor(Type componentType, DefinePropertyAttribute attribute, Attribute[] attributes)
    {
      return new DefinedPropertyDescriptor(attribute.SafePropertyName, componentType, attribute.Expression, attributes);
    }

    #endregion Methods

    #region Overrides

    public override PropertyDescriptorCollection GetProperties()
    {
      if(properties == null)
      {
        List<PropertyDescriptor> items = new List<PropertyDescriptor>();
        foreach(PropertyDescriptor propertyDescriptor in base.GetProperties())
        {
          items.Add(propertyDescriptor);
        }//for

        foreach(DefinePropertyAttribute attribute in Attribute.GetCustomAttributes(ComponentType, typeof(DefinePropertyAttribute)))
        {
          Debug.Assert(!attribute.IsDefaultAttribute(), "!attribute.IsDefaultAttribute()");
          PropertyDescriptor propertyDescriptor = CreateDefinePropertyDescriptor(attribute);
          items.Add(propertyDescriptor);
        }//for

        properties = new PropertyDescriptorCollection(items.ToArray(), true);
      }//if

      return properties;
    }

    public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
      if(attributes != null && attributes.Length > 0)
      {
        List<PropertyDescriptor> items = new List<PropertyDescriptor>();
        foreach(PropertyDescriptor propertyDescriptor in GetProperties())
        {
          if(propertyDescriptor.Attributes.Contains(attributes))
          {
            items.Add(propertyDescriptor);
          }//if
        }//for
        return new PropertyDescriptorCollection(items.ToArray(), true);
      }//if

      return GetProperties();
    }

    #endregion Overrides
  }
}


// DefinePropertyTypeDescriptor.DefinedPropertyDescriptor.cs

using System;
using System.ComponentModel;
using System.Diagnostics;

namespace MyCompany.ComponentModel
{
  partial class DefinePropertyTypeDescriptor
  {
    #region class DefinedPropertyDescriptor

    private class DefinedPropertyDescriptor : PropertyDescriptor
    {
      #region Fields

      private readonly Type componentType;
      private readonly string expression;
      private PropertyDescriptor propertyDescriptor = null;

      #endregion Fields

      #region Constructors/Finalizer

      public DefinedPropertyDescriptor(string name, Type componentType, string expression, Attribute[] attributes)
        : base(name, attributes)
      {
        if(componentType == null)
        {
          throw new ArgumentNullException("componentType");
        }//if

        this.componentType = componentType;
        this.expression = expression;
      }

      #endregion Constructors/Finalizer

      #region Properties

      public string Expression
      {
        [DebuggerStepThrough]
        get { return expression; }
      }

      public PropertyDescriptor EvaluatedPropertyDescriptor
      {
        [DebuggerStepThrough]
        get
        {
          return propertyDescriptor ?? (propertyDescriptor = EvaluateExpression());
        }
      }

      #endregion Properties

      #region Methods

      private PropertyDescriptor EvaluateExpression()
      {
        PropertyDescriptor current = null;
        foreach(string propertyName in Expression.Split(DefinePropertyAttribute.PropertySeparator))
        {
          PropertyDescriptorCollection properties = (current == null) ? TypeDescriptor.GetProperties(ComponentType) : current.GetChildProperties();
          Debug.Assert(properties != null, "properties != null");
          if((current = properties[propertyName]) == null)
          {
            throw new InvalidOperationException();
          }//if
        }//for
        Debug.Assert(current != null, "current != null");
        return current;
      }

      private PropertyDescriptor EvaluateExpression(ref object component)
      {
        PropertyDescriptor current = null;
        foreach(string propertyName in Expression.Split(DefinePropertyAttribute.PropertySeparator))
        {
          PropertyDescriptorCollection properties = (current == null) ? TypeDescriptor.GetProperties(component) : current.GetChildProperties(component = current.GetValue(component));
          Debug.Assert(properties != null, "properties != null");
          if((current = properties[propertyName]) == null)
          {
            throw new InvalidOperationException();
          }//if
        }//for
        Debug.Assert(current != null, "current != null");
        return current;
      }

      #endregion Methods

      #region Overrides

      public override Type ComponentType
      {
        [DebuggerStepThrough]
        get { return componentType; }
      }

      public override Type PropertyType
      {
        [DebuggerStepThrough]
        get { return EvaluatedPropertyDescriptor.PropertyType; }
      }

      public override bool IsReadOnly
      {
        [DebuggerStepThrough]
        get { return EvaluatedPropertyDescriptor.IsReadOnly; }
      }

      public override object GetValue(object component)
      {
        PropertyDescriptor propertyDescriptor = EvaluateExpression(ref component);
        return propertyDescriptor.GetValue(component);
      }

      public override void SetValue(object component, object value)
      {
        PropertyDescriptor propertyDescriptor = EvaluateExpression(ref component);
        propertyDescriptor.SetValue(component, value);
      }

      public override bool CanResetValue(object component)
      {
        PropertyDescriptor propertyDescriptor = EvaluateExpression(ref component);
        return propertyDescriptor.CanResetValue(component);
      }

      public override void ResetValue(object component)
      {
        PropertyDescriptor propertyDescriptor = EvaluateExpression(ref component);
        propertyDescriptor.ResetValue(component);
      }

      public override bool ShouldSerializeValue(object component)
      {
        PropertyDescriptor propertyDescriptor = EvaluateExpression(ref component);
        return propertyDescriptor.ShouldSerializeValue(component);
      }

      #endregion Overrides
    }

    #endregion class DefinedPropertyDescriptor
  }
}


Enjoy и welcome с критикой.
... << RSDN@Home 1.2.0 alpha rev. 650>>
Now playing: «Тихо в лесу…»
Help will always be given at Hogwarts to those who ask for it.
Re[2]: [C# 2.0] Complex data-binding со свойствами вложенных
От: vgrigor  
Дата: 08.06.06 12:12
Оценка:
А ты всегда пишешь вообще без комментариев?

И сам потом все помнишь что у тебя написано?
Винтовку добудешь в бою!
Re[3]: [C# 2.0] Complex data-binding со свойствами вложенных
От: _FRED_ Черногория
Дата: 08.06.06 13:53
Оценка:
Здравствуйте, vgrigor, Вы писали:

V>А ты всегда пишешь вообще без комментариев?

V>И сам потом все помнишь что у тебя написано?

На самом деле да

В данном конкретном случае меня может оправдать разве лишь то сам я ничего, кроме класса аттрибута, не написал, только реализовал :о). Описание всех методов есть в МСДН — в базовых класах и интерфейсах. как использовать поле Expression аттрибута я показал. но, если что-то нечсно, я постараюсь, как смогу рассказать.
... << RSDN@Home 1.2.0 alpha rev. 652>>
Now playing: «Тихо в лесу…»
Help will always be given at Hogwarts to those who ask for it.
Re: [C# 2.0] Complex data-binding со свойствами вложенных об
От: Serjio Россия  
Дата: 09.09.08 07:25
Оценка:
Здравствуйте, _FRED_, Вы писали:

> Я не считаю его удачным


почему ?
Только на РСДН помимо ответа на вопрос, можно получить еще список орфографических ошибок и узнать что-то новое из грамматики английского языка (c) http://www.rsdn.ru/forum/cpp/4720035.1.aspx
Автор: ZOI4
Дата: 28.04.12
Re[2]: [C# 2.0] Complex data-binding со свойствами вложенных
От: _FRED_ Черногория
Дата: 24.07.09 16:20
Оценка:
Здравствуйте, Serjio, Вы писали:

>> Я не считаю его удачным


S>почему ?


Извиняюсь за задержку с ответом

Данное решение было предназначено для следующей задачи: имеется набор связанных между собой бизнес-объектов. Хотелось (не мне, а тому, кто ставил передо мной задачи) просто разметить бизнес-объекты, что бы их напрямую можно было бы использовать для биндинга в представлении.

Получается, что логика представления (доступные в представлении поля) задаются в бизнес-моделе, тогда как должно быть наоборот: уровень представления должен определять, что (а не только "как") показывать.

Если вы хотите иметь "тонкого клиента", который только знает "как", но не должен знать "что", то вам, ИМХО, всё равно лучше на сервере разделить бизнес-объекты и объекты-представления и отдавать клиенту "объекты-представления".

Отдельные объекты-представления позволяют менять представление независимо от бизнес-модели, но, да: незначительно увеличится стоимость обновления представления при изменении бизнес-объектов. Но тут уже всё дело в используемых инструментальных средствах
Help will always be given at Hogwarts to those who ask for it.
Re[3]: [C# 2.0] Complex data-binding со свойствами вложенных
От: MozgC США http://nightcoder.livejournal.com
Дата: 24.07.09 19:34
Оценка:
А хорошего враппера реализующего INotifyPropertyChanged, IEditableObject, IChangeTracking, поддерживающего коллекции и т.д. у тебя не завалялось?
Re[4]: [C# 2.0] Complex data-binding со свойствами вложенных
От: _FRED_ Черногория
Дата: 24.07.09 20:25
Оценка:
Здравствуйте, MozgC, Вы писали:

MC>А хорошего враппера реализующего INotifyPropertyChanged, IEditableObject, IChangeTracking, поддерживающего коллекции и т.д. у тебя не завалялось?


Нет, я предпочитаю каждый раз делать что-то новое исходя из имеющихся условий. В BLToolKit.ComponentModel смотрел?
Help will always be given at Hogwarts to those who ask for it.
Re[2]: [C# 2.0] Complex data-binding со свойствами вложенных
От: _FRED_ Черногория
Дата: 12.04.11 05:18
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>>Исходники — в ответе.


По сообщениям доброжелателей, данный код перестал работать под четвёртым фреймворком, вызывая StackOverflowException. В связи с чем, было сделано одно небольшое изменение, которое будет работать под фреймворками от второго до четвёртого. Под более ранними не проверял.

Определение класса DefinePropertyTypeDescriptionProvider<T> должно быть проще:
// DefinePropertyTypeDescriptionProvider`1.cs
namespace MyCompany.ComponentModel
{
  public class DefinePropertyTypeDescriptionProvider<T> : TypeDescriptionProvider
  {
    #region Constructors\Finalizer

    public DefinePropertyTypeDescriptionProvider() : base(TypeDescriptor.GetProvider(typeof(T))) { }

    #endregion Constructors\Finalizer

    #region Overrides

    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) {
      return new DefinePropertyTypeDescriptor(objectType, instance, base.GetTypeDescriptor(objectType, instance));
    }

    #endregion Overrides
  }
}

_FR>Enjoy и welcome с критикой.
Help will always be given at Hogwarts to those who ask for it.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.