PropertyGrid позволяет отображать разнообразные структуры классов в удобном для редактирования виде и для этого достаточно связать с ним объект вашего класса. Однако, не все конструкции можно сразу отобразить без написания дополнительного кода. В этой статье я хочу рассказать о своем опыте использования PropertyGrid в контексте:
1. Отображение выпадающего списка записей, который собой представляет коллекцию объектов.

2. Отображение поля, связанного с коллекцией объектов, вызывающего редактор коллекции.

В качестве примера я взял задачу для реализации управления информацией о сотруднике, которую реализует класс Employee.Класс содержит два свойства. Первое — свойство типа JobTitle (должность сотрудника для выпадающего списка), второе – свойство типа JobTitleCollection (коллекция должностей сотрудников для редактора коллекции). Задачей является отображение объекта класса Employee в PropertyGrid так, как показано на двух рисунках в начале статьи, а именно:1. Отобразить поле, в котором пользователь сможет выбрать одну из должностей сотрудника с помощью выпадающего списка как в элементе ComboBox.2. Отобразить поле, которое позволит вызвать редактор связанной коллекции должностей, а после завершения изменения коллекции отобразить должности в PropertyGrid в виде списка.
Рассмотрим подробнее.
Класс сотрудника
Нужно реализовать класс для преобразования типа JobTitle в такой вид, чтобы объект PropertyGrid смог корректно отобразить выпадающий список должностей. Предполагается, что в момент создания объекта класса Employee будет добавлено несколько должностей в коллекцию для отображения в списке, после чего коллекция сможет отобразиться в выпадающем списке с дальнейшим редактированием через поле JobTitleCollection объекта класса Employee.Сначала нужно реализовать класс преобразования типа должности в строковое отображение и обратно — JobTitleTypeConverter. Далее, помимо этого класса будут реализованы дополнительные классы, необходимые для решения задачи.
Для вас доступен исходный код примера. Ссылка продублирована в конце статьи.
Сразу хочу отметить, что управление информацией объекта, отображаемой в PropertyGrid, осуществляется с помощью атрибутов. Это позволяет задать требуемый стиль отображения информации. Далее атрибуты будут активно использоваться в реализации решения.
Для реализации класса коллекции должностей нужно его унаследовать от класса CollectionBase и реализовать интерфейс ICustomTypeDescriptor. Это позволит использовать коллекцию для корректного отображения в PropertyGrid. Для этого используется реализация класса описателя свойств коллекции JobTitleCollectionPropertyDescriptor.
Статья создана по мотивам PropertyGrid FAQ и имеет своей целью его дополнение.
1. Отображение выпадающего списка записей, который собой представляет коллекцию объектов.

2. Отображение поля, связанного с коллекцией объектов, вызывающего редактор коллекции.

В качестве примера я взял задачу для реализации управления информацией о сотруднике, которую реализует класс Employee.Класс содержит два свойства. Первое — свойство типа JobTitle (должность сотрудника для выпадающего списка), второе – свойство типа JobTitleCollection (коллекция должностей сотрудников для редактора коллекции). Задачей является отображение объекта класса Employee в PropertyGrid так, как показано на двух рисунках в начале статьи, а именно:1. Отобразить поле, в котором пользователь сможет выбрать одну из должностей сотрудника с помощью выпадающего списка как в элементе ComboBox.2. Отобразить поле, которое позволит вызвать редактор связанной коллекции должностей, а после завершения изменения коллекции отобразить должности в PropertyGrid в виде списка.
Рассмотрим подробнее.
Класс сотрудника
- /// <summary>
- /// Информация о сотруднике
- /// </summary>
- [DisplayName("Сотрудник"), Description("Информация о сотруднике"), Category("Информация")]
- public class Employee
- {
- private JobTitle jt = new JobTitle();
- /// <summary>
- /// Должность, свойство отображается как выпадающий список должностей с возможностью выбора одной записи
- /// (объекта JobTitle)
- /// </summary>
- [DisplayName("Должность"), Description("Должность сотрудника"), Category("Выбор из списка")]
- [TypeConverter(typeof(JobTitleTypeConverter))]
- public JobTitle jobtitle
- {
- get { return jt; }
- set { jt = value; }
- }
-
- private JobTitleCollection jobTitles = new JobTitleCollection();
- /// <summary>
- /// Коллекция должностей, отображается как поле вызова редактора коллекции объектов
- /// с возможностью развернуть свойство в виде списка всех возможных должностей в PropertyGrid
- /// </summary>
- [Description("Коллекция должностей сотрудников"), Category("Коллекции"), DisplayName("Должности")]
- [TypeConverter(typeof(JobTitleCollectionConverter))]
- public JobTitleCollection JobTitles
- {
- get { return jobTitles; }
- set { jobTitles = value; }
- }
- }
Класс должности JobTitle
- /// <summary>
- /// Должность сотрудника
- /// </summary>
- [DisplayName("Должность"), Description("Должность сотрудника"), Category("Информация о сотруднике")]
- [TypeConverter(typeof(JobTitleConverter))]
- public class JobTitle : MyItem
- {
- /// <summary>
- /// Конструктор
- /// </summary>
- public JobTitle() { }
-
- /// <summary>
- /// Конструктор
- /// </summary>
- /// <param name="ID">ID записи</param>
- /// <param name="ItemName">Название</param>
- public JobTitle(int ID, string ItemName)
- {
- this.ID = ID;
- this.ItemName = ItemName;
- }
- }
Решение задачиНужно реализовать класс для преобразования типа JobTitle в такой вид, чтобы объект PropertyGrid смог корректно отобразить выпадающий список должностей. Предполагается, что в момент создания объекта класса Employee будет добавлено несколько должностей в коллекцию для отображения в списке, после чего коллекция сможет отобразиться в выпадающем списке с дальнейшим редактированием через поле JobTitleCollection объекта класса Employee.Сначала нужно реализовать класс преобразования типа должности в строковое отображение и обратно — JobTitleTypeConverter. Далее, помимо этого класса будут реализованы дополнительные классы, необходимые для решения задачи.
Для вас доступен исходный код примера. Ссылка продублирована в конце статьи.
Сразу хочу отметить, что управление информацией объекта, отображаемой в PropertyGrid, осуществляется с помощью атрибутов. Это позволяет задать требуемый стиль отображения информации. Далее атрибуты будут активно использоваться в реализации решения.
- /// <summary>
- /// Класс преобразования типа JobTitle
- /// Выполняет преобразование к типу String и обратно в JobTitle
- /// Также, позволяет отображать в виде выпадающего списка коллекцию должностей
- /// </summary>
- public class JobTitleTypeConverter : ExpandableObjectConverter
- {
- /// <summary>
- /// Возвращает значение, показывающее, поддерживает ли объект стандартный набор значений,
- /// которые можно выбрать из списка.
- /// Если не установить принудительно значение в true, есть вероятность,
- /// что обработчик не будет воспринимать ваш метод GetStandardValues и вы не увидите
- /// ваш список значений для выбора
- /// </summary>
- /// <param name="context"></param>
- /// <returns></returns>
- public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
- {
- return true;
- }
-
- /// <summary>
- /// Этот метод можно исключить, но в перспективе он позволит использовать данный класс конвертора
- /// для вызова из разных классов.
- /// Для этого достаточно добавить в секцию case имя другого класса, который должен содержать свойство
- /// JobTitle с функцией связи с выпадающим списком
- /// </summary>
- /// <param name="context"></param>
- /// <returns></returns>
- private JobTitleCollection GetCollection(System.ComponentModel.ITypeDescriptorContext context)
- {
- JobTitleCollection collection = new JobTitleCollection();
- switch (context.Instance.GetType().Name)
- {
- case "Employee":
- collection = ((Employee)context.Instance).JobTitles;
- break;
- default:
- collection = ((Employee)context.Instance).JobTitles;
- break;
- }
- return collection;
- }
-
- /// <summary>
- /// Метод возвращает список значений из коллекции должностей для отображения в выпадающем
- /// списке значений PropertyGrid для должности сотрудника
- /// </summary>
- /// <param name="context"></param>
- /// <returns></returns>
- public override TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
- {
- return new StandardValuesCollection(GetCollection(context));
- }
-
- /// <summary>
- /// Метод проверяет, можно ли преобразовывать полученное значение свойства от пользователя
- /// в нужный нам тип
- /// </summary>
- /// <param name="context"></param>
- /// <param name="destinationType"></param>
- /// <returns></returns>
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType.Equals(typeof(string)))
- {
- return true;
- }
- else
- {
- return false;
- }
- }
-
- /// <summary>
- /// Преобразование типа JobTitle в строку для отображения значения в поле PropertyGrid
- /// </summary>
- /// <param name="context"></param>
- /// <param name="culture"></param>
- /// <param name="value"></param>
- /// <param name="destinationType"></param>
- /// <returns></returns>
- public override object ConvertTo(ITypeDescriptorContext context,
- System.Globalization.CultureInfo culture, object value, Type destinationType)
- {
- if (destinationType == typeof(string) && value is JobTitle)
- {
- JobTitle item = (JobTitle)value;
- return item.FullName;
- }
- return base.ConvertTo(context, culture, value, destinationType);
- }
-
- /// <summary>
- /// Проверка на возможность обратного преобразования строкового представления в тип JobTitle
- /// </summary>
- /// <param name="context"></param>
- /// <param name="sourceType"></param>
- /// <returns></returns>
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType.Equals(typeof(string)))
- {
- return true;
- }
- else
- {
- return false;
- }
- }
-
- /// <summary>
- /// Преобразование строкового представления должности в тип JobTitle.
- /// Недостаток данного преобразования - правильность работы приведения типа зависит от формата
- /// строкового представления типа JobTitle, а именно, от дублирующихся значений.
- /// </summary>
- /// <param name="context"></param>
- /// <param name="culture"></param>
- /// <param name="value"></param>
- /// <returns></returns>
- public override object ConvertFrom(ITypeDescriptorContext context,
- System.Globalization.CultureInfo culture, object value)
- {
- if (value.GetType() == typeof(string))
- {
- JobTitle itemSelected = GetCollection(context).Count.Equals( 0) ?
- new JobTitle() : GetCollection(context)[ 0];
-
- foreach (JobTitle Item in GetCollection(context))
- {
- string sCraftName = Item.FullName;
- if (sCraftName.Equals((string)value))
- {
- itemSelected = Item;
- }
- }
- return itemSelected;
- }
- else
- return base.ConvertFrom(context, culture, value);
- }
-
- /// <summary>
- /// Возвращает значение, показывающее, является ли исчерпывающим списком коллекция стандартных значений,
- /// возвращаемая методом.
- ///
- /// false - данные можно вводить вручную
- /// true - только выбор из списка
- /// </summary>
- /// <param name="context"></param>
- /// <returns></returns>
- public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
- {
- return true;
- }
-
- /// <summary>
- /// Конструктор метода
- /// </summary>
- public JobTitleTypeConverter() { }
- }
-
Класс коллекции должностейДля реализации класса коллекции должностей нужно его унаследовать от класса CollectionBase и реализовать интерфейс ICustomTypeDescriptor. Это позволит использовать коллекцию для корректного отображения в PropertyGrid. Для этого используется реализация класса описателя свойств коллекции JobTitleCollectionPropertyDescriptor.
- /// <summary>
- /// Класс коллекции должностей
- /// Содержит методы, предоставляющие информацию для описания содержимого свойств коллекции
- /// и отображения в PropertyGrid
- /// </summary>
- [DisplayName("Перечень должностей"), Description("Перечень должностей сотрудников"), Category("Информация о сотруднике")]
- public class JobTitleCollection : CollectionBase, ICustomTypeDescriptor
- {
- #region Методы коллекции
- public void Add(JobTitle Item)
- {
- this.List.Add(Item);
- }
-
- public void Remove(JobTitle Item)
- {
- this.List.Remove(Item);
- }
-
- public JobTitle this[int Index]
- {
- get { return (JobTitle)this.List[Index]; }
- }
- #endregion
-
- #region Реализация интерфейса ICustomTypeDescriptor
- public String GetClassName()
- {
- return TypeDescriptor.GetClassName(this, true);
- }
-
- public AttributeCollection GetAttributes()
- {
- return TypeDescriptor.GetAttributes(this, true);
- }
-
- public String GetComponentName()
- {
- return TypeDescriptor.GetComponentName(this, true);
- }
-
- public TypeConverter GetConverter()
- {
- return TypeDescriptor.GetConverter(this, true);
- }
-
- public EventDescriptor GetDefaultEvent()
- {
- return TypeDescriptor.GetDefaultEvent(this, true);
- }
-
- public PropertyDescriptor GetDefaultProperty()
- {
- return TypeDescriptor.GetDefaultProperty(this, true);
- }
-
- public object GetEditor(Type editorBaseType)
- {
- return TypeDescriptor.GetEditor(this, editorBaseType, true);
- }
-
- public EventDescriptorCollection GetEvents(Attribute[] attributes)
- {
- return TypeDescriptor.GetEvents(this, attributes, true);
- }
-
- public EventDescriptorCollection GetEvents()
- {
- return TypeDescriptor.GetEvents(this, true);
- }
-
- public object GetPropertyOwner(PropertyDescriptor pd)
- {
- return this;
- }
-
- public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
- {
- return GetProperties();
- }
-
- public PropertyDescriptorCollection GetProperties()
- {
- PropertyDescriptorCollection pds = new PropertyDescriptorCollection(null);
-
- for (int i = 0; i < this.List.Count; i++)
- {
- JobTitleCollectionPropertyDescriptor pd = new JobTitleCollectionPropertyDescriptor(this, i);
- pds.Add(pd);
- }
- return pds;
- }
- #endregion
- }
Дополнительный класс описания свойств коллекции объектов JobTitle
- /// <summary>
- /// Класс описания свойств коллекции объектов JobTitle
- /// </summary>
- public class JobTitleCollectionPropertyDescriptor : PropertyDescriptor
- {
- private JobTitleCollection collection = null;
- private int index = -1;
-
- public JobTitleCollectionPropertyDescriptor(JobTitleCollection coll, int idx) :
- base("#" + idx.ToString(), null)
- {
- this.collection = coll;
- this.index = idx;
- }
-
- public override AttributeCollection Attributes
- {
- get
- {
- return new AttributeCollection(null);
- }
- }
-
- public override bool CanResetValue(object component)
- {
- return true;
- }
-
- public override Type ComponentType
- {
- get
- {
- return this.collection.GetType();
- }
- }
-
- public override string DisplayName
- {
- get
- {
- JobTitle Item = this.collection[index];
- return Item.FullName;
- }
- }
-
- public override string Description
- {
- get
- {
- JobTitle Item = this.collection[index];
- StringBuilder sb = new StringBuilder();
- sb.Append(Item.ItemName);
- sb.Append(", ");
- sb.Append(index + 1);
-
- return sb.ToString();
- }
- }
-
- public override object GetValue(object component)
- {
- return this.collection[index];
- }
-
- public override bool IsReadOnly
- {
- get { return false; }
- }
-
- public override string Name
- {
- get { return "#" + index.ToString(); }
- }
-
- public override Type PropertyType
- {
- get { return this.collection[index].GetType(); }
- }
-
- public override void ResetValue(object component)
- {
- }
-
- public override bool ShouldSerializeValue(object component)
- {
- return true;
- }
-
- public override void SetValue(object component, object value)
- {
-
- }
- }
Дополнительный класс конвертора коллекции должностей
- /// <summary>
- /// Класс преобразователя стандартного отображения коллекции в PropertyGrid
- /// Заменяет обозначение "(Collection)" на "(Должности...)"
- /// </summary>
- public class JobTitleCollectionConverter : ExpandableObjectConverter
- {
- public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture,
- object value, Type destType)
- {
- if (destType == typeof(string) && value is JobTitleCollection)
- {
- return "(Должности...)";
- }
- return base.ConvertTo(context, culture, value, destType);
- }
- }
Дополнительный класс конвертора типа должности
- /// <summary>
- /// Класс выполняющий преобразование типа JobTitle для корректного отображения в редакторе коллекций
- /// Если его не реализовать, то в окне редактора коллекции JobTitle будут отображаться внутреннее имя
- /// класса JobTitle
- /// </summary>
- public class JobTitleConverter : ExpandableObjectConverter
- {
- public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture,
- object value, Type destType)
- {
- if (destType == typeof(string) && value is JobTitle)
- {
- JobTitle jobtitle = (JobTitle)value;
- return jobtitle.FullName;
- }
- return base.ConvertTo(context, culture, value, destType);
- }
- }
Класс предок классов сотрудника и должности
- /// <summary>
- /// Класс-предок сущностей
- /// </summary>
- public class MyItem
- {
- private int iID = -1;
- /// <summary>
- /// ID записи
- /// </summary>
- [Description("Номер элемента"), Category("Коллекции"), DisplayName("Номер")]
- public int ID
- {
- get { return iID; }
- set { iID = value; }
- }
-
- private string sItemName = "";
- /// <summary>
- /// Название
- /// </summary>
- [Description("Название элемента"), Category("Коллекции"), DisplayName("Название")]
- public string ItemName
- {
- get { return sItemName; }
- set { sItemName = value; }
- }
-
- /// <summary>
- /// Полное наименование объекта
- /// </summary>
- [Browsable(false)]
- public string FullName
- {
- get
- {
- return String.Format("{0} №{1}", ItemName, ID.ToString());
- }
- }
- }
Вы можете загрузить исходный код примера.Статья создана по мотивам PropertyGrid FAQ и имеет своей целью его дополнение.