ネストしたプロパティと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日
« デフォルト値とシリアライゼーション ネストしたプロパティを展開する »