ネストしたプロパティとTypeConverter

カスタムコントロール作成入門講座中級編

TypeConverterを作る前に、まず影に関するプロパティを1つのクラスにまとめます。また、影の方向を表すプロパティも追加します。

public enum ShadowDirection
{
    ToBottomRight,
    ToTopLeft
}

public class Shadow
{
    public Shadow()
    {
        Color = SystemColors.GrayText;
        Depth = 1;
        Direction = ShadowDirection.ToBottomRight;
    }

    [DefaultValue(typeof(Color), "GrayText")]
    public Color Color { get; set; }

    [DefaultValue(1)]
    public int Depth { get; set; }

    [DefaultValue(typeof(ShadowDirection), "ToBottomRight")]
    public ShadowDirection Direction { get; set; }
}
Public Enum ShadowDirection
    ToBottomRight
    ToTopLeft
End Enum

Public Class Shadow
    <DefaultValue(GetType(Color), "GrayText")>  _
    Public Property Color As Color = SystemColors.GrayText

    <DefaultValue(1)>  _
    Public Property Depth As Integer = 1

    <DefaultValue(GetType(ShadowDirection), "ToBottomRight")>  _
    Public Property Direction As ShadowDirection = ShadowDirection.ToBottomRight
End Class

コントロールの中身も、このクラスを使用するように変更します。

public partial class CustomControl1 : Control
{
    private Shadow _shadow = new Shadow();

    [Category("表示")]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public Shadow Shadow
    {
        get { return _shadow; }
        set { _shadow = value; Invalidate(); }
    }

    protected override void OnPaint(PaintEventArgs pe)
    {
        base.OnPaint(pe);

        int startPos = _shadow.Direction == ShadowDirection.ToBottomRight ? 0 : _shadow.Depth;
        using(Brush brush = new SolidBrush(_shadow.Color))
        {
            for (int i = 1; i <= _shadow.Depth; i++)
            {
                int pos = _shadow.Direction == ShadowDirection.ToBottomRight ? i : -i;
                pe.Graphics.DrawString(Text, Font, brush, startPos+pos, startPos+pos);
            }
        }
        using (Brush brush = new SolidBrush(ForeColor))
        {
            pe.Graphics.DrawString(Text, Font, brush, startPos, startPos);
        }
    }
}
Public Class CustomControl1 : Inherits Control
    Private _shadow As New Shadow()

    <category("表示")> _
    <DesignerSerializationVisibility(DesignerSerializationVisibility.Content)> _
    Public Property Shadow() As Shadow
        Get : Return _shadow : End Get
        Set : _shadow = value : Invalidate() : End Set
    End Property

    Protected Overrides Sub OnPaint(pe As PaintEventArgs)
        MyBase.OnPaint(pe)

        Dim startPos As Integer = If(_shadow.Direction = ShadowDirection.ToBottomRight, 0, _shadow.Depth)
        Using brush As Brush = New SolidBrush(_shadow.Color)
            For i As Integer = 1 To _shadow.Depth
                Dim pos As Integer = If(_shadow.Direction = ShadowDirection.ToBottomRight, i, -i)
                pe.Graphics.DrawString(Text, Font, brush, startPos+pos, startPos+pos)
            Next
        End Using
        Using brush As Brush = New SolidBrush(ForeColor)
            pe.Graphics.DrawString(Text, Font, brush, startPos, startPos)
        End Using
    End Sub
End Class

DesignerSerializationVisibilityは、シリアライズに関する指定で、Contentの場合はオブジェクト全体をシリアライズするという意味になります。指定しなかった場合はVisibleと同じになります。また、シリアライズしないことを指定するにはHiddenにします。

では、いよいよType Converterの登場です。自前のType Converterは、TypeConverterクラスを継承し、名前はClassNameConverterにするのが慣例です。Shadowクラスの場合は、ShadowConverterというクラス名にします。

まずは4つあるメソッドの内の2つを紹介します。なお、VB.NETではSystem.ComponentModelをImportsする必要があります。

public class ShadowConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if (sourceType == typeof(string))
            return true;
        else
            return base.CanConvertFrom(context, sourceType);
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        if (destinationType == typeof(string))
            return true;
        else
            return base.CanConvertTo(context, destinationType);
    }
}
Public Class ShadowConverter: Inherits TypeConverter
    Public Overrides Function CanConvertFrom(context As ITypeDescriptorContext, sourceType As Type) As Boolean
        If sourceType = GetType(String) Then
            Return True
        Else
            Return MyBase.CanConvertFrom(context, sourceType)
        End If
    End Function

    Public Overrides Function CanConvertTo(context As ITypeDescriptorContext, destinationType As Type) As Boolean
        If destinationType = GetType(String) Then
            Return True
        Else
            Return MyBase.CanConvertTo(context, destinationType)
        End If
    End Function
End Class

CanConvertFromはsourceTypeからオブジェクトへの、CanConvertToはオブジェクトからdestinationTypeへの変換が可能かどうかを返すメソッドです。ここでは、いずれのメソッドとも、対象が文字列の場合にtrueを返し、それ以外の場合は親であるTypeConverterクラスに委ねています。

CanConvertFromメソッドやCanConvertToメソッドがtrueを返すと、引き続き、実際に変換を行うConvertFromやConvertToメソッドが呼ばれます。

    public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    {
        string strValue = value as string;
        if (strValue == null)
            return base.ConvertFrom(context, culture, value);
        string[] values = strValue.Split(',');
        int count = values.Length;
        try
        {
            Shadow shadow = new Shadow();
            if (count < 1 || values[0].Trim().Length == 0)
            {
                shadow.Color = SystemColors.GrayText;
            }
            else
            {
                ColorConverter colorConverter = new ColorConverter();
                shadow.Color = (Color)colorConverter.ConvertFromString(values[0]);
            }
            if (count < 2)
                shadow.Depth = 1;
            else
                shadow.Depth = int.Parse(values[1]);
            if (count < 3)
                shadow.Direction = ShadowDirection.ToBottomRight;
            else
                shadow.Direction = (ShadowDirection)Enum.Parse(typeof(ShadowDirection), values[2], true);
            return shadow;
        }
        catch
        {
            throw new ArgumentException("プロパティの値が無効です");
        }
    }

    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
        Shadow shadow = value as Shadow;
        if (shadow == null || destinationType != typeof(string))
            return base.ConvertTo(context, culture, value, destinationType);
        ColorConverter colorConverter = new ColorConverter();
        return string.Format("{0}, {1}, {2}",
                             colorConverter.ConvertToString(shadow.Color),
                             shadow.Depth, shadow.Direction);
    }
    Public Overrides Function ConvertFrom(context As ITypeDescriptorContext, culture As System.Globalization.CultureInfo, value As Object) As Object
        Dim strValue As String = TryCast(value, String)
        If strValue Is Nothing Then
            Return MyBase.ConvertFrom(context, culture, value)
        End If
        Dim values As String() = strValue.Split(","C)
        Dim count As Integer = values.Length
        Try
            Dim shadow As New Shadow()
            If count < 1 OrElse values(0).Trim().Length = 0 Then
                shadow.Color = SystemColors.GrayText
            Else
                Dim colorConverter As New ColorConverter()
                shadow.Color = DirectCast(colorConverter.ConvertFromString(values(0)), Color)
            End If
            If count < 2 Then
                shadow.Depth = 1
            Else
                shadow.Depth = Integer.Parse(values(1))
            End If
            If count < 3 Then
                shadow.Direction = ShadowDirection.ToBottomRight
            Else
                shadow.Direction = DirectCast([Enum].Parse(GetType(ShadowDirection), values(2), True), ShadowDirection)
            End If
            Return shadow
        Catch
            Throw New ArgumentException("プロパティの値が無効です")
        End Try
    End Function

    Public Overrides Function ConvertTo(context As ITypeDescriptorContext, culture As System.Globalization.CultureInfo, value As Object, destinationType As Type) As Object
        Dim shadow As Shadow = TryCast(value, Shadow)
        If (shadow Is Nothing) OrElse (destinationType <> GetType(System.String)) Then
            Return MyBase.ConvertTo(context, culture, value, destinationType)
        End If
        Dim colorConverter As New ColorConverter()
        Return String.Format("{0}, {1}, {2}", _
                             colorConverter.ConvertToString(shadow.Color), _
                             shadow.Depth, shadow.Direction)
    End Function

ConvertFromではまず、カンマ区切りの文字列をSplitで分解します。分解した文字列の数が3つに満たない場合はデフォルト値を設定するようにしているため、コードがやや長くなっています。

Shadow.Colorでは、ColorConverterという既存のConverterを使って変換します。文字列が正しくない色の場合は例外が発生します。他にも、FontConverter、PointConverter、SizeConverter、Int32Converter、EnumConverterなど、数多くのConverterが用意されています。

Shadow.Directionでは、列挙体の基本クラスであるEnumのParseメソッドを使って変換します。文字列が列挙体にない値の場合は例外が発生します。

ConvertToでは、Formatメソッドを使って文字列を作り出します。

ConvertFromもConvertToも、valueが変換元の型として、想定していないものの場合は、親クラスの各メソッドを呼び出しています。

最後に、ShadowクラスにShadowConverterを使うように指示しましょう。

[TypeConverter(typeof(ShadowConverter))]
public class Shadow
{ ... }
<TypeConverter(GetType(ShadowConverter))> _
Public Class Shadow
...
End Class

では、F6キーでコンパイルして、デバッグ用アプリケーションのプロパティウィンドウでShadowの文字列を変更してみてください。ShadowプロパティのsetでInvalidate()を呼んでいるので、文字列を変更するとコントロールに反映されますね。

« »

コメントをどうぞ

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

« »