ネストしたプロパティと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()を呼んでいるので、文字列を変更するとコントロールに反映されますね。
2014年1月10日
« デフォルト値とシリアライゼーション ネストしたプロパティを展開する »