Практическое руководство. Реализация преобразователя типов

Преобразователь типов можно использовать для преобразования значений из одного типа данных в другой, а также при настройке свойств во время разработки; роль преобразователя заключается в обеспечении преобразования текста в значение или предоставлении возможности выбора значений из выпадающего списка. При правильной настройке преобразователь типов может выдавать код для настройки свойства, для чего используются объекты InstanceDescriptor и System.Reflection, предоставляющие системе сериализации конструктора информацию, необходимую для создания кода, инициализирующего свойство во время выполнения.

Использование преобразователей типов для преобразования значений

Преобразователи типов можно использовать для преобразований строк в значения или преобразований в поддерживаемые типы данных (или из них) во время разработки и во время выполнения. На таком узле, как обозреватель свойств в конструкторе форм, преобразователи типов позволяют представлять пользователю значение свойства в виде текста, а также преобразовывать введенный пользователем текст в значение соответствующего типа данных.

Большинство исходных типов данных (Int32, String, перечисляемые типы и другие) имеют используемые по умолчанию преобразователи типов, обеспечивающие преобразование строк в значения и выполняющие проверку допустимости. Используемые по умолчанию преобразователи типов содержатся в пространстве имен System.ComponentModel, а их имена образованы по следующему правилу: ИмяПреобразователяТиповConverter. Когда предоставляемые по умолчанию функциональные возможности недостаточны для выполнения поставленных целей, преобразователь типов можно расширить, а при определении пользовательского типа, не имеющего связанного с ним преобразователя, можно реализовать настраиваемый преобразователь типов.

Примечание

Чтобы сопоставить свойство или член данных с преобразователем типа, для свойства или члена данных обычно указывается атрибут TypeConverterAttribute.Если атрибут TypeConverterAttribute уже указан для некоторого типа, его не нужно заново задавать для свойств или членов данных этого типа.

Реализация преобразователя типов не зависит от функций пользовательского интерфейса. Следовательно, один и тот же преобразователь типов может применяться как для Windows Forms, так и для Web Forms.

Реализация преобразователя простых типов, который может осуществлять преобразование строки в точку

  1. Определите класс, производный от класса TypeConverter.

  2. Переопределите метод CanConvertFrom, который указывает, из какого типа преобразователь может осуществлять преобразование. Этот метод является перегруженным.

  3. Переопределите метод ConvertFrom, реализующий преобразование. Этот метод является перегруженным.

  4. Переопределите метод CanConvertTo, который указывает тип, в который преобразователь может осуществлять преобразование. Для преобразования в строковый тип переопределение данного метода не требуется. Этот метод является перегруженным.

  5. Переопределите метод ConvertTo, реализующий преобразование. Этот метод является перегруженным.

  6. Переопределите метод IsValid(ITypeDescriptorContext, Object), осуществляющий проверку. Этот метод является перегруженным.

В следующем примере кода реализуется преобразователь типов, преобразующий тип String в тип Point, а Point — в String. В данном примере методы CanConvertTo и IsValid(ITypeDescriptorContext, Object) не переопределены.

Option Explicit 
Option Strict

Imports System
Imports System.ComponentModel
Imports System.Globalization
Imports System.Drawing

Public Class PointConverter
   Inherits TypeConverter
   
   ' Overrides the CanConvertFrom method of TypeConverter.
   ' The ITypeDescriptorContext interface provides the context for the
   ' conversion. Typically, this interface is used at design time to 
   ' provide information about the design-time container.
   Public Overrides Overloads Function CanConvertFrom(context As ITypeDescriptorContext, sourceType As Type) As Boolean
      If sourceType Is GetType(String) Then
         Return True
      End If
      Return MyBase.CanConvertFrom(context, sourceType)
   End Function
   
   ' Overrides the ConvertFrom method of TypeConverter.
   Public Overrides Overloads Function ConvertFrom(context As ITypeDescriptorContext, culture As CultureInfo, value As Object) As Object
      If TypeOf value Is String Then
         Dim v As String() = CStr(value).Split(New Char() {","c})
         Return New Point(Integer.Parse(v(0)), Integer.Parse(v(1)))
      End If
      Return MyBase.ConvertFrom(context, culture, value)
   End Function
   
   ' Overrides the ConvertTo method of TypeConverter.
   Public Overrides Overloads Function ConvertTo(context As ITypeDescriptorContext, culture As CultureInfo, value As Object, destinationType As Type) As Object
      If destinationType Is GetType(String) Then
         Return CType(value, Point).X & "," & CType(value, Point).Y
      End If
      Return MyBase.ConvertTo(context, culture, value, destinationType)
   End Function
End Class
using System;
using System.ComponentModel;
using System.Globalization;
using System.Drawing;

public class PointConverter : TypeConverter {
   // Overrides the CanConvertFrom method of TypeConverter.
   // The ITypeDescriptorContext interface provides the context for the
   // conversion. Typically, this interface is used at design time to 
   // provide information about the design-time container.
   public override bool CanConvertFrom(ITypeDescriptorContext context, 
      Type sourceType) {
      
      if (sourceType == typeof(string)) {
         return true;
      }
      return base.CanConvertFrom(context, sourceType);
   }
   // Overrides the ConvertFrom method of TypeConverter.
   public override object ConvertFrom(ITypeDescriptorContext context, 
      CultureInfo culture, object value) {
      if (value is string) {
         string[] v = ((string)value).Split(new char[] {','});
         return new Point(int.Parse(v[0]), int.Parse(v[1]));
      }
      return base.ConvertFrom(context, culture, value);
   }
   // Overrides the ConvertTo method of TypeConverter.
   public override object ConvertTo(ITypeDescriptorContext context, 
      CultureInfo culture, object value, Type destinationType) {  
      if (destinationType == typeof(string)) {
         return ((Point)value).X + "," + ((Point)value).Y;
      }
      return base.ConvertTo(context, culture, value, destinationType);
   }
}

Преобразователи типов, предоставляющие список стандартных значений для окна свойств

Преобразователь типов может предоставлять список значений для типа в элементе управления окна "Свойства". Если преобразователь типов предоставляет набор стандартных значений типа, в поле ввода значения свойства для соответствующего типа в элементе управления окна "Свойства" отображается направленная вниз стрелка, с помощью которой можно показать набор значений свойства, предоставив возможность установить нужное значение с помощью мыши.

Если свойство типа, с которым связан данный преобразователь типов, выбрано в обозревателе свойств среды разработки, в поле ввода значения появляется кнопка, отображающая раскрывающийся список стандартных значений типа свойства, из которого можно выбрать нужное значение.

Реализация преобразователя простых типов, предоставляющего в обозревателе свойств раскрывающийся список стандартных значений

  1. Определите класс, производный от класса TypeConverter.

  2. Переопределите метод GetStandardValuesSupported и возвратите значение true.

  3. Переопределите метод GetStandardValues и возвратите коллекцию StandardValuesCollection, содержащую стандартные значения для типа свойства. Тип стандартных значений для свойства должен совпадать с типом свойства.

  4. Переопределите метод CanConvertFrom и возвратите значение параметра true для значения параметра строкового типа sourceType.

  5. Переопределите метод ConvertFrom и возвратите соответствующее значение для свойства, основанное на параметре value.

  6. Задайте атрибут TypeConverterAttribute, указывающий тип преобразователя, для типа, которому предоставляется набор стандартных значений.

В следующем примере демонстрируется преобразователь типов, который для свойства связанного с ним типа предоставляет список стандартных значений элементу управления окна "Свойства". Данный преобразователь типов работает со связанными с ним свойствами целочисленного типа. Чтобы использовать данный пример в Visual Studio, его следует откомпилировать в виде библиотеки классов, а также добавить в Панель элементов компонент IntStandardValuesControl. Добавьте к форме в режиме конструктора экземпляр элемента управления IntStandardValuesControl и, воспользовавшись прокруткой, найдите свойство TestInt в окне "Свойства" при выбранном элементе управления. При выборе поля ввода значения для данного свойства отображается направленная вниз стрелка, при щелчке по которой выдается список стандартных значений. При вводе целочисленного значения оно будет не только добавлено к списку стандартных значений, но и установлено для данного свойства.

using System;
using System.ComponentModel;
using System.Collections;
using System.Drawing;
using System.Windows.Forms;

namespace StandardValuesTest
{  
    public class StandardValuesIntConverter : System.ComponentModel.TypeConverter
    {
        private ArrayList values;
        public StandardValuesIntConverter()
        {
            // Initializes the standard values list with defaults.
            values = new ArrayList(new int[] { 1, 2, 3, 4, 5 });
        }

        // Indicates this converter provides a list of standard values.
        public override bool GetStandardValuesSupported(System.ComponentModel.ITypeDescriptorContext context)
        {
            return true;
        }

        // Returns a StandardValuesCollection of standard value objects.
        public override System.ComponentModel.TypeConverter.StandardValuesCollection GetStandardValues(System.ComponentModel.ITypeDescriptorContext context)
        {        
            // Passes the local integer array.
            StandardValuesCollection svc = 
                new StandardValuesCollection(values);       
            return svc;
        }

        // Returns true for a sourceType of string to indicate that 
        // conversions from string to integer are supported. (The 
        // GetStandardValues method requires a string to native type 
        // conversion because the items in the drop-down list are 
        // translated to string.)
        public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Type sourceType)
        {
            if( sourceType == typeof(string) )
                return true;
            else 
                return base.CanConvertFrom(context, sourceType);
        }

        // If the type of the value to convert is string, parses the string 
        // and returns the integer to set the value of the property to. 
        // This example first extends the integer array that supplies the 
        // standard values collection if the user-entered value is not 
        // already in the array.
        public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            if( value.GetType() == typeof(string) )
            {
                // Parses the string to get the integer to set to the property.
                int newVal = int.Parse((string)value);
            
                // Tests whether new integer is already in the list.
                if( !values.Contains(newVal) )
                {
                    // If the integer is not in list, adds it in order.
                    values.Add(newVal);
                    values.Sort();
                }                                
                // Returns the integer value to assign to the property.
                return newVal;
            }
            else
                return base.ConvertFrom(context, culture, value);
        }
    }

    // Provides a test control with an integer property associated with 
    // the StandardValuesIntConverter type converter.
    public class IntStandardValuesControl : System.Windows.Forms.UserControl
    {
        [TypeConverter(typeof(StandardValuesIntConverter))]
        public int TestInt
        {
            get
            {
                return this.integer_field;
            }
            set
            {
                if(value.GetType() == typeof(int))
                    this.integer_field = value;
            }
        }
        private int integer_field = 0;
      
        public IntStandardValuesControl()
        {
            this.BackColor = Color.White;
            this.Size = new Size(472, 80);
        }

        // OnPaint override displays instructions for the example.
        protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
        {
            if(this.DesignMode)
            {
                e.Graphics.DrawString("TypeConverter.GetStandardValues Example Control", new Font(FontFamily.GenericMonospace, 10), new SolidBrush(Color.Blue), 5, 5);
                e.Graphics.DrawString("The type converter for the TestInt property of this", new Font(FontFamily.GenericMonospace, 10), new SolidBrush(Color.Black), 5, 20);
                e.Graphics.DrawString("component provides a list of standard values to the", new Font(FontFamily.GenericMonospace, 10), new SolidBrush(Color.Black), 5, 30);
                e.Graphics.DrawString("Properties window. Setting a value through a property", new Font(FontFamily.GenericMonospace, 10), new SolidBrush(Color.Black), 5, 40);
                e.Graphics.DrawString("grid adds it to the list of standard values.", new Font(FontFamily.GenericMonospace, 10), new SolidBrush(Color.Black), 5, 50);             
            }
            else
            {
                e.Graphics.DrawString("TypeConverter.GetStandardValues Example Control", new Font(FontFamily.GenericMonospace, 10), new SolidBrush(Color.Blue), 5, 5);         
                e.Graphics.DrawString("This control was intended for use in design mode.", new Font(FontFamily.GenericMonospace, 10), new SolidBrush(Color.Black), 5, 20);       
            }
        }
    }
}
Imports System
Imports System.ComponentModel
Imports System.ComponentModel.Design
Imports System.Collections
Imports System.Drawing
Imports System.Windows.Forms

Namespace StandardValuesTest

    Public Class StandardValuesIntConverter
        Inherits System.ComponentModel.TypeConverter

        Private values As ArrayList

        Public Sub New()
            ' Initializes the standard values list with defaults.
            values = New ArrayList(New Integer() {1, 2, 3, 4, 5})
        End Sub 'New

        ' Indicates this type converter provides a list of standard values.
        Public Overloads Overrides Function GetStandardValuesSupported(ByVal context As System.ComponentModel.ITypeDescriptorContext) As Boolean
            Return True
        End Function 'GetStandardValuesSupported

        ' Returns a StandardValuesCollection of standard value objects.
        Public Overloads Overrides Function GetStandardValues(ByVal context As System.ComponentModel.ITypeDescriptorContext) As System.ComponentModel.TypeConverter.StandardValuesCollection
            ' Passes the local integer array.
            Dim svc As New StandardValuesCollection(values)
            Return svc
        End Function 'GetStandardValues

        ' Returns true for a sourceType of string to indicate that 
        ' conversions from string to integer are supported. (The 
        ' GetStandardValues method requires a string to native type 
        ' conversion because the items in the drop-down list are 
        ' translated to string.)
        Public Overloads Overrides Function CanConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal sourceType As System.Type) As Boolean
            If sourceType Is GetType(String) Then
                Return True
            Else
                Return MyBase.CanConvertFrom(context, sourceType)
            End If
        End Function 'CanConvertFrom

        ' If the type of the value to convert is string, parses the string 
        ' and returns the integer to set the value of the property to. 
        ' This example first extends the integer array that supplies the 
        ' standard values collection if the user-entered value is not 
        ' already in the array.
        Public Overloads Overrides Function ConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object) As Object
            If value.GetType() Is GetType(String) Then
                ' Parses the string to get the integer to set to the property.
                Dim newVal As Integer = Integer.Parse(CStr(value))

                ' Tests whether new integer is already in the list.
                If Not values.Contains(newVal) Then
                    ' If the integer is not in list, adds it in order.
                    values.Add(newVal)
                    values.Sort()
                End If
                ' Returns the integer value to assign to the property.
                Return newVal
            Else
                Return MyBase.ConvertFrom(context, culture, value)
            End If
        End Function 'ConvertFrom
    End Class 'StandardValuesIntConverter

    ' Provides a test control with an integer property associated with the 
    ' StandardValuesIntConverter type converter.
    Public Class IntStandardValuesControl
        Inherits System.Windows.Forms.UserControl

        <TypeConverter(GetType(StandardValuesIntConverter))> _
        Public Property TestInt() As Integer
            Get
                Return Me.integer_field
            End Get
            Set(ByVal Value As Integer)
                If Value.GetType() Is GetType(Integer) Then
                    Me.integer_field = Value
                End If
            End Set
        End Property
        Private integer_field As Integer = 0

        Public Sub New()
            Me.BackColor = Color.White
            Me.Size = New Size(472, 80)
        End Sub 'New

        ' OnPaint override displays instructions for the example.
        Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
            If Me.DesignMode Then
                e.Graphics.DrawString("TypeConverter.GetStandardValues Example Control", New Font(FontFamily.GenericMonospace, 10), New SolidBrush(Color.Blue), 5, 5)
                e.Graphics.DrawString("The type converter for the TestInt property of this", New Font(FontFamily.GenericMonospace, 10), New SolidBrush(Color.Black), 5, 20)
                e.Graphics.DrawString("component provides a list of standard values to the", New Font(FontFamily.GenericMonospace, 10), New SolidBrush(Color.Black), 5, 30)
                e.Graphics.DrawString("Properties window. Setting a value through a property", New Font(FontFamily.GenericMonospace, 10), New SolidBrush(Color.Black), 5, 40)
                e.Graphics.DrawString("grid adds it to the list of standard values.", New Font(FontFamily.GenericMonospace, 10), New SolidBrush(Color.Black), 5, 50)
            Else
                e.Graphics.DrawString("TypeConverter.GetStandardValues Example Control", New Font(FontFamily.GenericMonospace, 10), New SolidBrush(Color.Blue), 5, 5)
                e.Graphics.DrawString("This control was intended for use in design mode.", New Font(FontFamily.GenericMonospace, 10), New SolidBrush(Color.Black), 5, 20)
            End If
        End Sub 'OnPaint
    End Class 'IntStandardValuesControl
End Namespace 'StandardValuesTest

Преобразователи типов, генерирующие код для инициализации свойств во время выполнения

В режиме разработки платформа .NET Framework предоставляет возможность генерации динамического кода инициализации, с помощью которого во время выполнения будет инициализироваться свойство.

Разработчики могут создать преобразователь типов, формирующий инициализирующий код на основе конструктора. Такие преобразователи позволяют настраивать свойства типа во время выполнения программы, используя при динамической генерации кода конструктора значения, установленные во время разработки. Для настройки типа и значений конструктора для свойства преобразователь типов реализует определенную последовательность операций.

Если помимо конструктора необходимо сформировать дополнительный код для инициализации свойства, можно использовать динамическую генерацию кода, реализовав пользовательский класс CodeDomSerializer и воспользовавшись атрибутом DesignerSerializerAttribute, который сопоставляет CodeDomSerializer типа с самим типом. Обычно такой подход используется только в сценариях, где возможность динамического контроля или настройки процедуры генерации кода инициализации компонента имеет большое значение. Дополнительные сведения по данной методике см. в документации, описывающей CodeDomSerializer.

Для создания пользовательского инициализатора свойств по типу конструктора следует связать преобразователь типов с типом свойства; при этом преобразователь должен быть способен преобразовывать в InstanceDescriptor.

Реализация преобразователя типов, формирующего код инициализации свойства по типу конструктора

  1. Определите класс, производный от класса TypeConverter.

  2. Переопределите метод CanConvertTo. Если параметр destinationType совпадает с типом InstanceDescriptor, возвратите значение true.

  3. Переопределите метод ConvertTo. Если параметр destinationType совпадает с типом InstanceDescriptor, нужно создать и возвратить InstanceDescriptor, предоставляющий конструктор и его аргументы, для которых создается код. Для создания дескриптора InstanceDescriptor, предоставляющего соответствующий конструктор и параметры, следует извлечь ConstructorInfo из типа Type инициализируемого свойства, вызвав для этого метод GetConstructor или GetConstructors с соответствующей сигнатурой метода искомого конструктора. Затем создайте новый экземпляр дескриптора и передайте ConstructorInfo для типа, представляющего тип используемого конструктора, вместе с массивом объектов параметров, соответствующих сигнатуре конструктора.

В следующем примере реализуется преобразователь типов, позволяющий создавать инициализирующий код свойства по типу конструктора для свойств типа Point.

public class PointConverter : TypeConverter 
{
   public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) 
   {
      if (destinationType == typeof(InstanceDescriptor)) 
         return true;
      return base.CanConvertTo(context, destinationType);
   }

public override object ConvertTo(ITypeDescriptorContext context, 
CultureInfo culture, object value, Type destinationType) 
{
      // Insert other ConvertTo operations here.
      //
      if (destinationType == typeof(InstanceDescriptor) && 
value is Point) 
   {
         Point pt = (Point)value;

      ConstructorInfo ctor = typeof(Point).GetConstructor(
new Type[] {typeof(int), typeof(int)});
      if (ctor != null) 
      {
         return new InstanceDescriptor(ctor, new object[] {pt.X, pt.Y});
}
}
   return base.ConvertTo(context, culture, value, destinationType);      
}

Компиляция кода

  • При разработке пользовательского TypeConverter рекомендуется установить номер сборки, возрастающий после каждого построения. Это предотвращает создание в среде разработки старых кэшированных версий TypeConverter.

См. также

Основные понятия

Обобщенное преобразование типов