Сообщений 1 Оценка 30 [+0/-1] Оценить |
Предисловие Реализация Использование Дополнение Заключение |
В одном из проектов мне необходимо было реализовать редактор свойств объекта. Загвоздка заключалась в том что в проекте не используются средства ORM, и ко мне данные, которые надо было отредактировать и отослать обратно, попадали просто как двумерный массив object[,]. Ужас! То есть бизнес-классы отсутствовали вообще, и данные просто загружались из базы данных, изменялись и снова сохранялись в БД.
Известным редактором свойств объекта является PropertyGrid. Данный элемент управления используется практически повсеместно. Он просто очень удобен и предоставляет интуитивно понятный интерфейс для редактирования различных типов данных (int, string, double, массивов и коллекций). Он, конечно, предоставляет огромные возможности для расширения функциональности и редактирования сложных типов данных.
Но проблема была в том что PropertyGrid может редактировать объекты с уже существующими свойствами. В моем же случае все данные хранятся в БД, и не существует соответствующих бизнес-классов, куда отображаются данные из БД. Следовательно, на первый взгляд, использовать PropertyGrid (со всеми его встроенными редакторами простых типов, массивов, коллекций и автоматической проверкой корректности введенного значения) не получится. Но это только на первый взгляд...
После некоторых изысканий элегантное решение было найдено. Использовать PropertyGrid все-таки можно. Просто необходимо создать некий класс DynamicObject, реализующий интерфейс ICustomTypeDescriptor:
public interface ICustomTypeDescriptor { AttributeCollection GetAttributes(); string GetClassName(); string GetComponentName(); TypeConverter GetConverter(); EventDescriptor GetDefaultEvent(); PropertyDescriptor GetDefaultProperty(); object GetEditor(Type editorBaseType); EventDescriptorCollection GetEvents(); EventDescriptorCollection GetEvents(Attribute[] attributes); PropertyDescriptorCollection GetProperties(); PropertyDescriptorCollection GetProperties(Attribute[] attributes); object GetPropertyOwner(PropertyDescriptor pd); } |
В этом классе нужно реализовать методы, которые предоставляют динамическую информацию об объекте и, в частности, о его свойствах. Вся работа будет сконцентрирована в основном вокруг реализации одного перегруженного метода – GetProperties. Данный метод будет вызван элементом управления PropertyGrid, когда вы присвоите экземпляр класса свойству SelectedObject:
var dynamicObject = new DynamicObject(); propertyGrid1.SelectedObject = dynamicObject; |
PropertyGrid вызовет метод GetProperties и передаст туда массив атрибутов Attribute[] с одним элементом BrowsableAttribute. То есть PropertyGrid запрашивает у объекта список свойств объекта, которые можно отобразить.
Метод GetProperties возвращает PropertyDescriptorCollection – коллекцию объектов PropertyDescriptor. PropertyDescriptor – это абстрактный класс, описывающий свойство объекта. В данном случае необходимо реализовать собственный класс-наследник PropertyDescriptor, который будет использоваться элементом управления PropertyGrid для получения информации о конкретном свойстве объекта. Так как тип свойства может быть любым типом из .Net Framework, то я решил реализовать generic-класс и назвал его просто – GenericPropertyDescriptor<T>, где T – тип свойства.
public class GenericPropertyDescriptor<T> : PropertyDescriptor { private T _value; public GenericPropertyDescriptor(string name, Attribute[] attrs) : base(name, attrs) { } public GenericPropertyDescriptor(string name, T value, Attribute[] attrs) : base(name, attrs) { _value = value; } publicoverridebool CanResetValue(object component) { returnfalse; } publicoverride System.Type ComponentType { get { returntypeof(GenericPropertyDescriptor<T>); } } publicoverrideobject GetValue(object component) { return _value; } publicoverridebool IsReadOnly { get { return Array.Exists(this.AttributeArray, attr => attr is ReadOnlyAttribute); } } publicoverride System.Type PropertyType { get { returntypeof(T); } } publicoverridevoid ResetValue(object component) { } publicoverridevoid SetValue(object component, object value) { _value = (T)value; } publicoverridebool ShouldSerializeValue(object component) { returnfalse; } } |
Вернемся к реализации класса-обертки DynamicObject. Как я уже говорил, метод GetProperties возвращает PropertyDescriptorCollection – коллекцию объектов PropertyDescriptor. Соответственно, в классе DynamicObject нужно где-то хранить описатели свойств. Для этого создадим private-поле типа PropertyDescriptorCollection.
private PropertyDescriptorCollection propertyDescriptors = new PropertyDescriptorCollection(null); |
По умолчанию коллекция объектов PropertyDescriptor пуста, и необходимо реализовать методы для изменения её содержимого. Для корректного отображения свойства в PropertyGrid и для инициализации класса GenericPropertyDescriptor<T> реализуем метод (назовем его AddProperty<T>), добавляющий новое свойство. Метод должен принимать имя свойства, его текущее значение, описание свойства для отображения в PropertyGrid, имя категории (если мы хотим, чтобы свойства в PropertyGrid были разбиты по категориям), флаг readOnly для указания, должно ли свойство быть доступным только для чтения, и массив атрибутов, на случай, если нужно пометить данное свойство дополнительными атрибутами.
ПРИМЕЧАНИЕ Дополнительные атрибуты могут понадобиться, например, для указания PropertyGrid специфического редактора или специального конвертора типа данных. |
Реализация перегруженного метода AddProperty<T> представлена ниже:
public void AddProperty<T>( string name, T value, string displayName, string description, string category, bool readOnly, IEnumerable<Attribute> attributes) { var attrs = attributes == null ? new List<Attribute>() : new List<Attribute>(attributes); if (!String.IsNullOrEmpty(displayName)) attrs.Add(new DisplayNameAttribute(displayName)); if (!String.IsNullOrEmpty(description)) attrs.Add(new DescriptionAttribute(description)); if (!String.IsNullOrEmpty(category)) attrs.Add(new CategoryAttribute(category)); if (readOnly) attrs.Add(new ReadOnlyAttribute(true)); propertyDescriptors.Add(new GenericPropertyDescriptor<T>( name, value, attrs.ToArray())); } publicvoid AddProperty<T>( string name, T value, string description, string category, bool readOnly) { AddProperty<T>(name, value, name, description, category, readOnly, null); } |
Также можно предусмотреть метод удаления свойств из коллекции свойств объекта. Для этого служит метод RemoveProperty:
public void RemoveProperty(string propertyName) { var descriptor = propertyDescriptors.Find(propertyName, true); if (descriptor != null) propertyDescriptors.Remove(descriptor); elsethrownew Exception("Property is not found"); } |
Кроме того, необходима возможность прочитать/изменить значение того или иного свойства извне элемента управления PropertyGrid. Для этого у класса DynamicObject нужно реализовать методы GetPropertyValue и SetPropertyValue:
private object GetPropertyValue(string propertyName) { var descriptor = propertyDescriptors.Find(propertyName, true); if (descriptor != null) return descriptor.GetValue(null); elsethrownew Exception("Property is not found"); } privatevoid SetPropertyValue(string propertyName, object value) { var descriptor = propertyDescriptors.Find(propertyName, true); if (descriptor != null) descriptor.SetValue(null, value); elsethrownew Exception("Property is not found"); } |
Ну и для удобства работы с классом DynamicObject добавим indexer, чтобы можно было обращаться к свойствам как к элементам коллекции:
public object this[string propertyName] { get { return GetPropertyValue(propertyName); } set { SetPropertyValue(propertyName, value); } } |
А теперь – пример использования, конечно. Для начала – инициализация объекта и добавление свойств. Предположим у нас есть простейшая форма и элемент управления PropertyGrid на форме. Здесь представлен код события Form1_Load:
private void Form1_Load(object sender, EventArgs e) { var dynamicObject = new DynamicObject(); dynamicObject.AddProperty<Int32>("Int32 Param", 0, "Int32 Param Description", "Simple types", false); dynamicObject.AddProperty<String>("String Param", "", "String Param Description", "Simple types", false); dynamicObject.AddProperty<Double>("Double Param", 0, "Double Param Description", "Simple types", false); dynamicObject.AddProperty<Int32[]>("Int32[] Param", new Int32[] { }, "Int32[] Param Description", "Array types", false); dynamicObject.AddProperty<String[]>("String[] Param", new String[] { }, "String[] Param Description", "Array types", false); dynamicObject.AddProperty<Double[]>("Double[] Param", new Double[] { }, "Double[] Param Description", "Array types", false); dynamicObject.AddProperty<List<Int32>>("List<Int32> Param", new List<Int32>(), "List<Int32> Param Description", "Collection types", false); dynamicObject.AddProperty<List<Double>>("List<Double> Param", new List<Double>(), "List<Double> Param Description", "Collection types", false); propertyGrid1.SelectedObject = dynamicObject; } |
А прочитать значения свойств объекта можно так:
private void btnRead_Click(object sender, EventArgs e) { var dynamicObject = propertyGrid1.SelectedObject as DynamicObject; if (dynamicObject != null) { var intValue = (Int32)dynamicObject["Int32 Param"]; var strValue = (String)dynamicObject["String Param"]; var dblValue = (Double)dynamicObject["Double Param"]; var intArray = (Int32[])dynamicObject["Int32[] Param"]; var strArray = (String[])dynamicObject["String[] Param"]; var dblArray = (Double[])dynamicObject["Double[] Param"]; var intList = (List<Int32>)dynamicObject["List<Int32> Param"]; var dblList = (List<Double>)dynamicObject["List<Double> Param"]; } } |
В тестовом проекте вы также найдете пример реализации фильтра свойств oбъекта DynamicObject, отображаемых в элементе управления PropertyGrid. Идея была мной позаимствована из новой WPF-версии элемента управления PropertyGrid, использованного в Visual Studio 2008. Подробно останавливаться на этом я не буду. Скажу просто, что если объект DynamicObject имеет более 20-30 свойств, очень удобно иметь возможность отфильтровать свойства по имени.
Данная статья описывает еще одну возможность (но далеко не последнюю в списке) из огромнейшего списка возможностей замечательного элемента управления PropertyGrid. Если вас заинтересовала данная статья, очень советую обратить внимание на статьюАлексея Кирюшкина, опубликованную в одном из номеров журнала RSDN Magazine, и на очень полезный сайт, специально посвященный PropertyGrid - Microsoft PropertyGrid Resource List.
Сообщений 1 Оценка 30 [+0/-1] Оценить |