« SizeGrip コンポーネント | トップページ | 構造体/クラスプロパティを展開可能にする(2) »

2010年10月 6日 (水)

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

C#を始めた頃からの疑問で、Size プロパティの挙動がある。Size プロパティは、Size 構造体のプロパティとあり、Size プロパティの説明では、Size 構造体のメンバに直接値を設定するな(しても大きさは変更されない)とある。これは、構造体が値型なので当然の挙動であるといえる。実際、初めての頃はプログラムコードでうっかり、button1.Size.Width = 100; とか記述して、コンパイルエラーになった記憶がある。

しかし一方で、プロパティウィンドウ上では、右図の様にそのメンバが展開され、しかもそのメンバに対して値が設定出来る(新しい値を入れるとその値で、大きさが変わる)。

プログラムコード上では、構造体として振る舞うくせに、プロパティウィンドウ上では、まるでクラス型の様な振る舞い方をするのである。なんで?と思っていたのであった。

つまり、プロパティウィンドウ上からは、Size.Width(Height) に対して値が変更出来てしまい、しかもちゃんと幅(高さ)が変わる。一方プログラムコードからは、この様な指定がを行っても、コンパイルで拒否されてしまう。

さて、以上の事が本来の疑問なのであるが、これを解き明かすには、単に Tips 的に書いても、それなりに長くなる。せっかく「研究所」と言っているからには、研究してみる事にしよう。

今回は、この疑問に関わる内容の基礎研究をする事にする。では、とにかく実際に構造体プロパティとクラスプロパティを作成して実験して見る事にする。

ここで Size 等、予め.NETで定義されているものではなく、独自の構造体/クラスを作成する。とりあえず Win32APIでは、比較的お世話になりやすい RECT 構造体から、C#独自の構造体/クラスとしてそれぞれ RectStruct,RectClass で定義してみた。

public struct RectStruct
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
    public RectStruct(int left, int top, int right, int bottom)
    {
        this.Left = left;
        this.Top = top;
        this.Right = right;
        this.Bottom = bottom;
    }
}
public class RectClass
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
    public RectClass(int left, int top, int right, int bottom)
    {
        this.Left = left;
        this.Top = top;
        this.Right = right;
        this.Bottom = bottom;
    }
}

また、これらをプロパティとして持つクラスを定義します。ここでは、プロパティウィンドウを使用する為に何かのコントロールにしたいので、TextBox プロパティから継承する事にして、

public class PropertyTestTextBox : TextBox
{
    private RectStruct rectStruct = new RectStruct(0,0,0,0);
    private RectClass rectClass = new RectClass(0,0,0,0);
    public RectStruct RectStruct
    {
        get
        {
            return rectStruct;
        }
        set
        {
            rectStruct = value;
        }
    }
    public RectClass RectClass
    {
        get
        {
            return rectClass;
        }
        set
        {
            rectClass = value;
        }
    }
}

と記述してみました。見ての通り、実際にはTextBoxには何の影響も与えないプロパティとなります。

これをコンパイルして出来た PropertyTestTextBox コントロールをフォームに貼り付けてみましょう。貼り付けた後に、そのプロパティウィンドウを見ると、

となり、構造体プロパティもクラスプロパティも、プロパティウィンドウ上には表示されるものの、展開可能な状態でもなく、編集も出来ない状態となる。
そう、この様な定義では、プログラムコード上でプロパティを使用する事が出来るだけで、設計時には設定が出来ません。これでは、プロパティウィンドウにプロパティを表示しても何の意味もありません。

では、futuremix 「C# プロパティエディタでクラスのプロパティを展開可能にするには」 で記述されている様な、展開可能にする簡単な呪文と唱えてみましょう。

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

コンパイルして、この呪文を有効化してから、プロパティエディタを見ると、

今度は灰色表示でなく、黒色表示になり、展開可能を思わせる+表示も付いています。しかし、+表示部分をクリックすると、+表示が消えるだけで展開もしません。

要するに、プロパティがないから展開しないという事です。という事で、きちんとプロパティ定義をします。

[TypeConverter(typeof(ExpandableObjectConverter))]
public struct RectStruct
{
    private int left;
    private int top;
    private int right;
    private int bottom;
    public RectStruct(int left, int top, int right, int bottom)
    {
        this.left = left;
        this.top = top;
        this.right = right;
        this.bottom = bottom;
    }
    public int Left
    {
        get
        {
            return left;
        }
        set
        {
            left = value;
        }
    }
    public int Top
    {
        get
        {
            return top;
        }
        set
        {
            top = value;
        }
    }
    public int Right
    {
        get
        {
            return right;
        }
        set
        {
            right = value;
        }
    }
    public int Bottom
    {
        get
        {
            return bottom;
        }
        set
        {
            bottom = value;
        }
    }
}
[TypeConverter(typeof(ExpandableObjectConverter))]
public class RectClass
{
    private int left;
    private int top;
    private int right;
    private int bottom;
    public RectClass(int left, int top, int right, int bottom)
    {
        this.left = left;
        this.top = top;
        this.right = right;
        this.bottom = bottom;
    }
    public int Left
    {
        get
        {
            return left;
        }
        set
        {
            left = value;
        }
    }
    public int Top
    {
        get
        {
            return top;
        }
        set
        {
            top = value;
        }
    }
    public int Right
    {
        get
        {
            return right;
        }
        set
        {
            right = value;
        }
    }
    public int Bottom
    {
        get
        {
            return bottom;
        }
        set
        {
            bottom = value;
        }
    }
}

今度は展開可能になりました。

RectClass プロパティは、初期値 0 以外の別の値を設定する事も可能です。RectStruct プロパティの方は、別の値を入れても初期値 0 に勝手に戻ります。まあ、これは構造体なので、構造体本来の挙動として当然です。

とにかくこれで展開可能になりました。まだ、RectClass,RectStructの項が、それぞれAcha_ya.SampleClass.RectClass,Acha_ya.SampleClass.RectStruct と表示されます。これは、表示だけなら、ToString メソッドをオーバーライドすれば、表示する事が出来る様です。RectClass,RectStruct それぞれの定義中に、

public override string ToString()
{
    return String.Format("{0}, {1}, {2}, {3}",
        this.Left, this.Top, this.Right, this.Bottom);
}

を入れてみましょう。今度は、

となり、RectClass,RectStruct の各欄は、自分のメンバ値を表示する様になります。但し、RectClass のメンバを変更しても、RectClass の部分の表示は変わりません。これは仕方がないのかもしれません。

以上、結構長くなりましたが、基礎調査部分は、これでおしまい。次回、今回も使用した TypeConverter(ExpandableObjectConverter) を本格的に使用しての本調査を行う事にします。最後に、ここで使用した最終形態のプロジェクト一式を、CABファイルにまとめて置いておきます。

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

« SizeGrip コンポーネント | トップページ | 構造体/クラスプロパティを展開可能にする(2) »

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

コメント

コメントを書く

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

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

トラックバック


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

« SizeGrip コンポーネント | トップページ | 構造体/クラスプロパティを展開可能にする(2) »