« 構造体/クラスプロパティを展開可能にする(1) | トップページ | 構造体/クラスプロパティを展開可能にする(3) »

2010年10月 7日 (木)

構造体/クラスプロパティを展開可能にする(2)

前回の「構造体/クラスプロパティを展開可能にする(1)」の続きです。実は前回分のタイトルやカテゴリーを変更しました。また、前回最後の時点でのソース一式もダウンロード可能なように記事をに追記しました。

さて、前回テスト用の構造体プロパティ・クラスプロパティはどちらも展開可能にはなりましたが、まだ最初の疑問であった Size プロパティ同様の動作はしていない。そこで、本格的にプロパティ展開動作を行う様に、前回ソースを書き換える事にする。
しかしその前に、前回最後に実装したToStringメソッドを削除しておく事にする。これは、以降で説明する TypeConverter を本格的に使用した方法で実現出来るので、単にプロパティウィンドウの RectStruct/RectClass 各項に文字列化したプロパティ値の表示目的だけに使用する必要はない事を証明するためである(削除しなくても動作に問題はない)。

[TypeConverter(typeof(ExpandableObjectConverter))]
public struct RectStruct
{
    ...
//  public override string ToString()
//  {
//      return String.Format("{0}, {1}, {2}, {3}",
//          this.Left, this.Top, this.Right, this.Bottom);
//  }
}
/// <summary>
/// WindowsAPIベースでおなじみのRECT構造体をクラス定義したもの
/// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))]
public class RectClass
{
    ...
//  public override string ToString()
//  {
//      return String.Format("{0}, {1}, {2}, {3}",
//          this.Left, this.Top, this.Right, this.Bottom);
//  }
}

では早速、TypeConverter の本格的に使用によるプロパティ展開を行なう事にする。
ここでは、MSDNの.NET Framework の PropertyGrid コントロールの高度な活用の「展開可能オブジェクトのサポートを提供するには」の項を参考にしながら、ExpandableObjectConverter を継承したクラス RectConverter を作成します。ここの例と同様に、CanConvertTo,ConvertTo,CanConvertFrom,ConvertFrom のメソッドをオーバーライドします。
記述内容も対象となるConvert対象のクラスが、SpellingOptions から RectStruct,RectClass になった事への対処以外は、全く同様です。(とは言っても、ConvertFrom は、Split メソッドを使用した記述に変更にして、ロジックを簡単化したので、変更箇所は多い)

public class RectConverter : ExpandableObjectConverter
{
    public override bool CanConvertTo(ITypeDescriptorContext context,
        System.Type destinationType) 
    {
        if (destinationType == typeof(RectStruct) ||
            destinationType == typeof(RectClass))
            return true;
        return base.CanConvertTo(context, destinationType);
    }

    public override object ConvertTo(ITypeDescriptorContext context,
        System.Globalization.CultureInfo culture, 
        object value, 
        System.Type destinationType) 
    {
        if (destinationType == typeof(System.String)) 
        {
            if (value is RectStruct)
            {
                RectStruct rs = (RectStruct)value;
                return String.Format("{0}, {1}, {2}, {3}",
                    rs.Left, rs.Top, rs.Right, rs.Bottom);
            }
            else if (value is RectClass)
            {
                RectClass rc = (RectClass)value;
                return String.Format("{0}, {1}, {2}, {3}",
                    rc.Left, rc.Top, rc.Right, rc.Bottom);
            }
        }
        return base.ConvertTo(context, culture, value, destinationType);
    }

    public override bool CanConvertFrom(ITypeDescriptorContext context,
        System.Type sourceType) 
    {
        if (sourceType == typeof(string))
            return true;

        return base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context,
        System.Globalization.CultureInfo culture, object value) 
    {
        if (value is string) 
        {
            if (context.PropertyDescriptor.PropertyType == typeof(RectStruct))
            {
                try 
                {
                    // 正しい文字列であれば、item[0]=Left値,item[1]=Top値,
                    // item[2]=Right値,item[3]=Bottom値の文字列が入る。
                    string[] item = ((string)value).Split(",".ToCharArray());

                    RectStruct rs = new RectStruct(int.Parse(item[0]),
                        int.Parse(item[1]), int.Parse(item[2]), int.Parse(item[3]));
                    return rs;
                }
                catch 
                {
                    throw new ArgumentException(
                        "文字列 '" + (string)value + 
                        "' を RectStruct 型に変換できません");
                }
            }
            else if (context.PropertyDescriptor.PropertyType == typeof(RectClass))
            {
                try 
                {
                    // 正しい文字列であれば、item[0]=Left値,item[1]=Top値,
                    // item[2]=Right値,item[3]=Bottom値の文字列が入る。
                    string[] item = ((string)value).Split(",".ToCharArray());

                    RectClass rc = new RectClass(int.Parse(item[0]),
                        int.Parse(item[1]), int.Parse(item[2]), int.Parse(item[3]));
                    return rc;
                }
                catch 
                {
                    throw new ArgumentException(
                        "文字列 '" + (string)value + 
                        "' を RectClass 型に変換できません");
                }
            }
        }  
        return base.ConvertFrom(context, culture, value);
    }
}

更にこの RectConverter を利用する様に、RectStruct,RectClass の定義を変更します。

[TypeConverter(typeof(RectConverter))]
public struct RectStruct
{
    ...
}
[TypeConverter(typeof(RectConverter))]
public class RectClass
{
    ...
}

これで、プロパティウィンドウのRectClass,RectStructの項からも値の設定が可能になります。

左図のプロパティウィンドウは、RectClass,RectStructが、どちらも初期値"0, 0, 0, 0"と表示されていたのを修正して、"1, 0, 0, 0"にしたときのものです。自動的に、Left プロパティの値も、0 から 1 に変わっています。

プロパティの展開可能な様にする Tips として、この ExpandableObjectConverter から拡張する方法が紹介されているHP/ブログはあります。しかし、クラスプロパティの例しか見た事がなかったので、構造体プロパティでも可能なのか?と疑問もありました。この実験結果から、クラスプロパティだけでなく、構造体プロパティも展開可能だという事が実証されました。

では、今度は構造体プロパティのメンバTopを変更してみましょう。Topに5を入力して、Enterキーを入力するとRectStructの欄にも5が自動的に反映されます。

つまり、プロパティウィンドウ上で構造体のメンバに直接値の設定が可能という事です。当初の疑問である、構造体プロパティのメンバに直接値を設定出来ないのに、プロパティウィンドウではなぜ可能なのか?という解答も、これで得る事が出来ました。

プロパティウィンドウは、TypeConverter の働きによって、メンバプロパティを親プロパティにコンバートしているのです。

しかし、まだ問題もあります。同様に RectClass の方のTopプロパティに5を入力して、Enterキーを入力すると、RectClassの欄には、5が反映されません。その後RectClassの欄にカーソルを入れるとか、RectStruct のメンバにまた値を入れるとかすれば、その時に反映されます。内部的には変更されているが、表示は更新されない様な動きとなっている様です。

ここで、この RectConverter クラス作成の手本としていたMSDNの例題では、これと同じ動きをするのだろうか?という疑問が出て来ました。

そこで、次回はちょっと脇道にそれて、MSDNの例題ではどういう動きをするのか検証を行なう事にします。また今回もこの時点で最終形態のプロジェクト一式を、CABファイルにまとめて置いておきます。

「PropertyTest2.cab」をダウンロード

« 構造体/クラスプロパティを展開可能にする(1) | トップページ | 構造体/クラスプロパティを展開可能にする(3) »

C#研究」カテゴリの記事

コメント

コメントを書く

コメントは記事投稿者が公開するまで表示されません。

(ウェブ上には掲載しません)

トラックバック


この記事へのトラックバック一覧です: 構造体/クラスプロパティを展開可能にする(2):

« 構造体/クラスプロパティを展開可能にする(1) | トップページ | 構造体/クラスプロパティを展開可能にする(3) »