Услуги Сертификаты Новости Статьи База знаний Алгоритмы Портфолио Скачать Ссылки Поиск
Услуги arrow Новости arrow Пример использования PropertyGrid в Visual Studio
Пример использования PropertyGrid в Visual Studio Версия для печати Отправить на e-mail
08.12.2009

В этой статье я хочу рассказать о своем опыте в использовании PropertyGrid в контексте:

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

Связь с динамическим списком коллекции объектов

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

Связь с динамической коллекцией объектов

PropertyGrid позволяет отобразить по умолчанию простые свойства объектов довольно легко, но если требуется нестандартные решения, подобно описанным выше, требуется небольшое расширение функционала объектов. Хотя, ничего сложного здесь тоже я не встретил.

Итак, в любой статье главное - практическая реализация решения. Что я и хочу здесь привести.

В качестве примера я взял задачу для управления информацией о сотруднике, которую реализует класс Employee. Класс содержит два свойства. Свойство о должности сотрудника и свойство коллекции должностей. Такой принцип я использовал для краткости примера, но функционал в результате не пострадал.

Текст класса снабжен комментариями, поэтому дополнительно код комментировать я не буду.

В результате реализации будет получен набор классов, позволяющий с помощью объекта 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; }
    }       
}

Класс должности

/// <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;
    }
}

Решение задачи № 1. Выпадающий список - коллекция объектов.

Нужно реализовать классы для преобразования типа JobTitle в такой вид, чтобы объект PropertyGrid смог корректно отобразить выпадающий список должностей. Предполагается, что изначально во время создания объекта Employee в свойство коллекции должностей было добавлено три записи должностей вручную.

Нужно реализовать класс JobTitleTypeConverter.

/// <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() { }
}

Класс коллекции должностей

/// <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());
        }
    }
}

Для тех, кто хочет подробнее просмотреть код реализации доступен исходный код с реализацией примера, реализованный с помощью .NET Framework 3.5 SP1 в Visual Studio 2008.

Последнее обновление ( 08.12.2009 )
 
< Пред.   След. >