Оценка 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.
Оценка 30
[+0/-1]
Оценить ![]() ![]() ![]() ![]() ![]() ![]()
|