Конвертор типа
Рассмотрим построение конвертора типа на примере класса SpaceSize, хранящего информацию об отступах слева, справа, сверху и снизу:
[Serializable] [TypeConverter(typeof(SpaceSizeConverter))] public struct SpaceSize { private int _left; private int _top; private int _right; private int _bottom;
public SpaceSize(int left, int top, int right, int bottom) { _left = left; _top = top; _right = right; _bottom = bottom; }
public int Left { get { return _left; } } public int Top { get { return _top; } } public int Right { get { return _right; } } public int Bottom { get { return _bottom; } } }
|
Как можно видеть, SpaceSize является неизменяемой (immutable) структурой. Поэтому сериализация свойства этого типа в код должна выглядеть как-то так:
this.MyControl2.Margins = new SpaceSize(5, 5, 5, 20);
|
Для этого мы определяем следующий конвертер типа:
public class SpaceSizeConverter : TypeConverter
|
Реализуем необходимые методы:
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(String)) return true; return base.CanConvertFrom(context, sourceType); }
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { if (destinationType == typeof(InstanceDescriptor)) return true; if (destinationType == typeof(string)) return true; return base.CanConvertTo(context, destinationType); }
|
В данном случае мы сообщаем, что можем создать свой тип из строки, а также вернуть строку и InstanceDescriptor, имея экземпляр своего типа. Не забывайте вызывать базовый класс, если не знаете как поступить с некоторым типом.
Далее рассмотрим конвертацию из строки в объект типа SpaceSize. Обратите внимание на комментарии в коде:
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string) { try { // убираем лишние пробелы слева и справа string str = ((string)value).Trim(); // мы не должны возвращать null, поскольку SpaceSize – value-type if (str.Length == 0) return new SpaceSize(0,0,0,0);
if (culture == null) // берем текущую культуру, если не задано culture = CultureInfo.CurrentCulture;
// разделяем строку на части используя разделитель списка из культуры string[] strs=((string)value).Split(culture.TextInfo.ListSeparator[0]); if (strs.Length != 4) // проверяем, что получилось 4 элемента throw new ArgumentException("Can not convert '" + (string)value + "' to type SpaceSize");
int[] nums = new int[strs.Length];
// используем конвертер для целых чисел и преобразуем каждый элемент TypeConverter typeConverter = TypeDescriptor.GetConverter(typeof(int)); for (int i = 0; i < nums.Length; i++) nums[i] = (int)typeConverter.ConvertFromString( context, culture, strs[i]); // создаем новый объект return new SpaceSize(nums[0], nums[1], nums[2], nums[3]); } catch { // грубая обработка ошибок throw new ArgumentException("Can not convert '" + (string)value + "' to type SpaceSize"); } } return base.ConvertFrom(context, culture, value); }
|
Таким образом, мы объяснили дизайнеру, как введенное пользователем в окне свойств значение, например, “32;32;10;10” преобразовать к объекту нашего типа. Заметьте, что мы использовали CultureInfo для получения разделителя списка, тем самым упростив жизнь людям в других странах, где список может разделяться другим символом. Аналогично мы не стали использовать int.Parse для строки, а использовали TypeConverter для типа int. В общем-то, разницы в данном конкретном случае нет, поскольку Int64Converter использует Convert.ToInt64 и Int64.Parse для выполнения этого преобразования, однако в более сложных случаях лучше использовать именно такой подход.
Теперь рассмотрим обратное преобразование, которое выполняется очень похоже:
public override object ConvertTo(ITypeDescriptorContext context,CultureInfo culture, object value, System.Type destinationType) { if (destinationType == typeof(string) && value is SpaceSize) { // преобразуем к нашему типу SpaceSize spaceSize = (SpaceSize)value; if (culture == null) // используем культуру по умолчанию, // если она не была передана в параметре culture = CultureInfo.CurrentCulture;
// получаем разделитель списка string str = culture.TextInfo.ListSeparator + " ";
// получаем конвертор типа для целых чисел // и преобразуем каждое свойство к строке TypeConverter typeConverter = TypeDescriptor.GetConverter(typeof(int)); string[] strs = new string[4]; int i = 0; strs[i++] = typeConverter.ConvertToString( context, culture, spaceSize.Left); strs[i++] = typeConverter.ConvertToString( context, culture, spaceSize.Top); strs[i++] = typeConverter.ConvertToString( context, culture, spaceSize.Right); strs[i++] = typeConverter.ConvertToString( context, culture, spaceSize.Bottom); // объединяем строки в одну и возвращаем результат return String.Join(str, strs); }
// здесь будет добавлена конвертация в InstanceDescriptor ...
return base.ConvertTo(context, culture, value, destinationType); }
|
В приведенном выше коде значения свойств преобразуются в строки с использованием информацию из культуры и конвертор типа int. Затем они объединяются, а для читаемости добавляется пробел между значениями. Теперь рассмотрим конвертацию в InstanceDescriptor:
if (destinationType == typeof(InstanceDescriptor) && value is SpaceSize) { SpaceSize spaceSize = (SpaceSize)value;
// получить информацию о конструкторе с данными типами параметров ConstructorInfo constructorInfo = typeof(SpaceSize).GetConstructor( new Type[]{ typeof(int), typeof(int), typeof(int), typeof(int) } );
if (constructorInfo != null) // создать описатель экземпляра из информации о конструкторе // и конкретных параметров конструктора return new InstanceDescriptor(constructorInfo, new object[]{spaceSize.Left, spaceSize.Top, spaceSize.Right, spaceSize.Bottom}); }
|
InstanceDescriptor будет использоваться для генерирования кода инициализации сериализуемого свойства. Он создает в методе InitializeComponent формы, на которую будет помещен наш контрол, код следующего вида:
this.MyControl2.Margins = new SpaceSize(5, 5, 5, 20);
|
Следующий код, будучи добавленным в конвертор типа, позволит создавать объекты, имея на руках имена свойств и их значения. Эти методы используются дизайнером в случае неизменяемого (immutable) объект (как SpaceSize). Однако как быть, если хочется редактировать отдельные свойства объекта, открытого в окне свойств? В этом случае при изменении свойств объект создается именно этим методом.
public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues) { return new SpaceSize((int)propertyValues["Left"], (int)propertyValues["Top"], (int)propertyValues["Right"], (int)propertyValues["Bottom"]); } public override bool GetCreateInstanceSupported(ITypeDescriptorContext context) { return true; }
|
Следующие два метода позволяют редактору свойств развернуть объект и показать свойства в отдельных ячейках в нужном порядке. Если это единственное, чего вы хотите от конвертера (разворачивание свойств), то достаточно использовать ExpandableObjectConverter.
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) { return TypeDescriptor.GetProperties(typeof(SpaceSize), attributes).Sort( new string[]{"Left", "Top", "Right", "Bottom"}); }
public override bool GetPropertiesSupported(ITypeDescriptorContext context) { return true; }
|
На этом написание конвертора закончено. Вы можете добавить свойство типа SpaceSize к своему контролу и посмотреть на него в дизайнере.
- Краткий путеводитель
- Проектируйте
- Сохраняйте гибкость
- Коллекции элементов
- Отрисовка
- Придерживайтесь стандартов
- Оптимизируйте
- Взаимодействие с мышью
- Взаимодействие с клавиатурой
- Поведение в режиме дизайна
- Использование атрибутов
- Коллекции
- Конверторы типов
- Расширение компонентов
- Локализация
- Источники данных
- Доступность
- Конвертор типа