ネストしたプロパティを展開する

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

前回の続きです。Shadowプロパティ、無事に読み書きできるようになりましたが、このままでは不便です。Fontプロパティのように、+を押すと展開できるようにしたいですよね。

実は、展開するのはすごく簡単です。TypeConverterを継承するのではなく、そのサブクラスであるExpandableObjectConverterを継承するように変えるだけです。

public class ShadowConverter : ExpandableObjectConverter
{}
Public Class ShadowConverter : Inherits ExpandableObjectConverter
End Class

プロパティウィンドウでShadowの項目を展開して、値を変えてみてください。ColorとDirectionの値を変えると、Shadowの文字列が変わりますよね。ところがDepthの値を変えてもShadowの文字列は変わりません。Depthの値が変わったことをShadowに伝える必要があるからです。それにはNotifyParent属性を使用するか、この後に説明するCreateInstanceメソッドのどちらかを使用します。

[DefaultValue(1)]
[NotifyParentProperty(true)]
public int Depth { get; set; }
<DefaultValue(1)> _
<NotifyParentProperty(True)> _
Public Property Depth As Integer = 1

また、DefaultValueを設定しているので、展開されたそれぞれの項目のリセットもできます。※なぜColorとDirection(enum型)にはなにも指定しなくてもShadowが変わるのかは、まだ判明していません。

しかし、まだ問題があります。展開された項目を変更しても、フォームに貼り付けたコントロールは再描画されません。再描画するためにはInvalidateを呼ばなければなりませんが、今まで書いたコードでInvalidateをしているのは、Shadowオブジェクトが新しいものに変わった時だけです。現在のShadowオブジェクトの個々の項目の値が変わっただけでは呼ばれないのです。

そこで、個々の値が変わった時に、Shadowオブジェクトを強制的に作り直す、という方法を取ることにします。そのためには、ShadowConverterに次のコードを追加します。

public override bool GetCreateInstanceSupported(ITypeDescriptorContext context)
{
    return true;
}

public override object CreateInstance(ITypeDescriptorContext context, System.Collections.IDictionary propertyValues)
{
    Shadow shadow = new Shadow();
    shadow.Color = (Color)propertyValues["Color"];
    shadow.Depth = (int)propertyValues["Depth"];
    shadow.Direction = (ShadowDirection)propertyValues["Direction"];
    return shadow;
}
Public Overrides Function GetCreateInstanceSupported(context As ITypeDescriptorContext) As Boolean
    Return True
End Function

Public Overrides Function CreateInstance(context As ITypeDescriptorContext, propertyValues As System.Collections.IDictionary) As Object
    Dim shadow As New Shadow()
    shadow.Color = DirectCast(propertyValues("Color"), Color)
    shadow.Depth = CInt(propertyValues("Depth"))
    shadow.Direction = DirectCast(propertyValues("Direction"), ShadowDirection)
    Return shadow
End Function

GetCreateInstanceSupportedがtrueを返す時(デフォルトはfalse)、いずれかの項目の値が変更されると、CreateInstanceが呼び出されます。CreateInstanceでは、新しいShadowオブジェクトを作り、渡された現在の値(Dictionary形式)で埋め、これをreturnします。

なお、この方法を取ると、個々の値のリセットができなくなります。しかし、Fontなど既存の展開可能なプロパティもこの方法を取っているので、それらの仕様に合わせるという意味でもこれでよいと思います。

順序が逆になりましたが、最後にShouldSerializeShadowとResetShadowを用意します。

public bool ShouldSerializeShadow()
{
    return _shadow.Color != SystemColors.GrayText ||
           _shadow.Depth != 1 ||
           _shadow.Direction != ShadowDirection.ToBottomRight;
}

public void ResetShadow()
{
    _shadow.Color = SystemColors.GrayText;
    _shadow.Depth = 1;
    _shadow.Direction = ShadowDirection.ToBottomRight;
    Invalidate();
}
Public Function ShouldSerializeShadow() As Boolean
    Return _shadow.Color <> SystemColors.GrayText OrElse _
           _shadow.Depth <> 1 OrElse _
           _shadow.Direction <> ShadowDirection.ToBottomRight
End Function

Public Sub ResetShadow()
    _shadow.Color = SystemColors.GrayText
    _shadow.Depth = 1
    _shadow.Direction = ShadowDirection.ToBottomRight
    Invalidate()
End Sub

ところで、Shadowクラスの場合はたまたま項目の定義順とソート順が一致していたために気になりませんでしたが、例えばSizeならHeight,Widthの順ではなく、Width,Heightの順にしたいはずです。最後にこの、好きな順にソートする方法を紹介します。ShadowConverterに次のコードを追加します。

public override bool GetPropertiesSupported(ITypeDescriptorContext context) { 
    return true; 
}

public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{ 
    PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(Shadow), attributes);
    return properties.Sort(new string[] {"Direction", "Color", "Depth"}); 
}
Public Overrides Function GetPropertiesSupported(context As ITypeDescriptorContext) As Boolean
    Return True
End Function

Public Overrides Function GetProperties(context As ITypeDescriptorContext, value As Object, attributes As Attribute()) As PropertyDescriptorCollection
    Dim properties As PropertyDescriptorCollection = TypeDescriptor.GetProperties(GetType(Shadow), attributes)
    Return properties.Sort(New String() {"Direction", "Color", "Depth"})
End Function

なお、このようにGetPropertiesを実装する場合は、ExpandableObjectConverterから継承する必要はなく、TypeConverterから継承することができます。

« »

コメントをどうぞ

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

« »