logo search
03

Конверторы типов

Если у вас есть некоторый собственный тип, который используется в качестве типа свойства в контроле, то дизайнер не будет знать, как с ним работать, до тех пор, пока вы ему этого не расскажете. Примерами таких типов могут быть Size, Point или Rectangle. Самый простой способ разрешить эту проблему – добавить простые свойства вроде Width и Height и редактировать через них, а составное свойство спрятать от дизайнера [Browsable(false)] и сериализатора [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]. Однако это далеко не всегда удобно, да и при большом количестве таких свойств это перегрузит окно свойств. Поэтому рекомендуется для таких типов создавать конвертор типа (TypeConverter). Конвертор типа – это класс, производный от TypeConverter или его наследника и определяющий ряд операций по преобразованию типов. Основными преобразованиями, используемыми дизайнером, являются преобразование в строку и из строки, а также преобразование в InstanceDescriptor, используемое при сериализации объекта в код. Пример конвертора вы можете найти в приложении к статье, здесь же перечислим основные вещи, которые необходимо помнить при создании своего конвертора.

Основными операциями конвертора являются следующие:

При реализации этих методов не забывайте вызывать базовый класс, если не знаете, как поступить с некоторым типом.

В большинство методов конвертора передаётся информация о культуре, с которой в настоящий момент нужно работать. Не игнорируйте этот параметр, используйте CultureInfo при операциях над списками, валютой, временем, датами и другими зависимыми от региональных установок типами данных. При отсутствии информации о культуре используйте CultureInfo.CurrentCulture. Например, если вы конвертируете из строки в ваш тип, и строка представляет список значений (например, “x; y”), то можно поступить так:

public override object ConvertFrom(ITypeDescriptorContext context,

CultureInfo culture, object value)

{

string stringValue = value as string;

if (stringValue != null)

{

// убираем лишние пробелы слева и справа

stringValue = stringValue.Trim();

if (culture == null)

// берем текущую культуру, если не задано

culture = CultureInfo.CurrentCulture;

// разделяем строку на части используя разделитель списка из культуры

string[] strs = stringValue.Split(culture.TextInfo.ListSeparator[0]);

// далее обрабатываем элементы списка

...

}

...

}

Для конвертации вложенных данных используйте TypeConverter.GetConverter(Type) для получения конвертера типа и используйте его методы ConvertTo, ConvertFrom соответственно. Старайтесь ничего не предполагать об используемых вами типах, особенно если они находятся в другой сборке – они могут измениться без вашего ведома. Вероятность того, что автор этих типов сохранит совместимость на уровне TypeConverter, значительно выше. Старайтесь при изменении типов, конструкторов, добавлении и изменении свойств сохранять обратную совместимость хотя бы на уровне своего конвертора. В этом случае формы, созданные с предыдущей версией вашего контрола смогут быть легко сконвертированы дизайнером автоматически.

Для сериализации в код предоставьте конвертацию в тип InstanceDescriptor используя ConstructorInfo. Если ваш объект целиком описывается конструктором, то используйте вариант InstanceDescriptor(MemberInfo, ICollection); Если необходимо дополнительное задание свойств объекта, используйте конструктор по умолчанию и InstanceDescriptor(MemberInfo, ICollection, bool); с последним параметром false. При этом в процессе сериализации в код для вашего объекта будут установлены все модифицируемые (settable) свойства, не равные значению по умолчанию (или для которых метод ShouldSerializeNNNN вернул true). Заметьте, что дизайнер не знает, что именно вы инициализировали в конструкторе, поэтому выставит все имеющиеся свойства, а следовательно передавать какие-либо данные в конструкторе особого смысла не имеет. При необходимости будет создана временная переменная внутри метода InitializeComponent, например, если сложный объект не является компонентом. Аналогично, если происходит сериализация коллекции элементов, не являющимися компонентами, то при полной инициализации конструктором объекты будут создаваться прямо внутри AddRange. В противном случае будут созданы временные объекты.

Если некоторое свойство возвращает ссылку на объект, можно сделать так, чтобы в окне свойств рядом с названием такого свойства появился «+», а при раскрытии ветки – возможность редактировать отдельные свойства объекта. В отсутствие setter-ов свойств, редактирование значений в окне свойств будет недоступно. Чтобы изменить это, можно реализовать методы CreateInstance и GetCreateInstanceSupported. В этом случае при изменении свойств объект создается заново при помощи CreateInstance. Если GetCreateInstanceSupported возвращает false, то редактировать свойства такого объекта будет нельзя.

Переопределять методы GetProperties и GetPropertiesSupported нужно, если вы хотите изменить список или порядок следования свойств, отображаемых в окне свойств. По умолчанию свойства отображаются в алфавитном порядке. Подробное описание механизмов переопределения свойств выходит за рамки этой статьи.

По умолчанию для классов, производных от Component используется ComponentConverter, который для свойства этого типа позволяет посмотреть его свойства и выбрать в выпадающем меню один из компонентов подходящего типа. Если раскрытие свойств не желательно, например, по причине большого их количества, используйте [TypeConverter(typeof(ReferenceConverter))] для свойства или всего типа.

Некоторые стандартные конверторы типов: