C# Programming

Flowers

Collection Editor

開発環境: Visual Studio 2003 

1.目次

2.目的

Windows.Forms で ListView がありますが、このプロパティを開くと Items というプロパティがあります。
プロパティの編集で、GUI ベースでコレクションの編集ができて便利ですよね。
自分用のコレクションクラスを作って同じようなことをやろうと思ったのですが、はまりました。^^;
だって、このセクションで書いてある内容について、MSのヘルプがぜんぜん役に立たないんだもん!まったく!!
結局、2週間近くの試行錯誤の結果、ようやく納得のいく方法にたどり着きました!
方法としては、リソースを使う方法と、リソースを使わないようにする方法の2つがありますが、2番目の方法がお勧めです。


どちらも忘れてしまうと、ヘルプにもほとんど何も書いてないのでもう二度と再現できそうもないので、メモっておきます。^^;

3.参考

(1) http://www.syncfusion.com/FAQ/WinForms/FAQ_c81c.asp
けっこうディープな内容。
(2) アトリビュート
(3) PingRadar

4.ListView の Item コレクションについて

ListView では、Items というコレクションのプロパティがあり、
これをダブルクリックすると次のようなコレクションエディタが開きます。

Image
ところが、これは ListViewItem コレクションという特別なプロパティで、
ArrayList のような配列をプロパティ宣言しただけではこのようなコレクションエディタを開くことができません。
同じようなことを独自のコレクションを作成してやろうと思うと、思う場合があると思います。

実は、PingRadar を作っているときに思いっきりはまってしまいました。^^;
簡単そうだけど、マニュアル化されてないので、試行錯誤するしかなく、まったく MSのヘルプには困ったものです。

5.独自のコレクションを作成し、コレクションエディタで編集する方法


そこで、ここでは独自のコレクションを作成し、コレクションエディタで編集する方法について説明します。

注意:
クラスライブラリを作成するため、Visual Studio.NET Professional 以上の開発環境が必要です。
Standard Edition では クラスライブラリをサポートしていないので、できません。


Step 1 Windows.Forms プロジェクトの新規作成
Windows.Forms アプリケーションの新規ソリューション(CollectionItem)を作成します。
Step 2 クラスライブラリを追加
コレクションエディタで編集するということは、コントロールとして Windows.Forms に貼り付けられるようにする必要があります。
そこで、ClassLibrary で、次のような MyControl を作成します。

ソリューションエクスプローラで、そのソリューションの右クリック→追加→新しいプロジェクト→クラスライブラリにより、MyControl という新しいクラスライブラリプロジェクトを追加します。
Step 3 クラスライブラリの実装
以下では、2つの例を解説しています。
1つは、MyItem を Object から継承する方法で、リソースを使う方法です。
もう1つは、MyItem を Component から継承する方法です。

クラスライブラリで作成する Windows.Control は、次のようなクラス構成になります。


ソースコード(リソースを使う方法:お勧めできない) 
Image


ソースコード(リソースを使わないようにする方法:こちらがお勧め)
Image


まずは、コレクションに追加するアイテムのクラス MyItem を作ります。
ソリューションエクスプローラで、クラスライブラリの右クリック→新しい項目の追加→クラスを選択し、MyItem.cs を作成します。
内部の実装は、ソースコードを参照してください。

次に、MyCollection クラスを作成します。同様に、MyCollection.cs を追加します。
これが MyItem を格納するクラスになります。このため、System.CollectionBase 以下の適当なクラスを使用します。
内部の実装は、ソースコードを参照してください。

注意:
CollectionEditor は、Item インデクサープロパティの返り値を リフレクションにより調べることによりコレクションのインスタンスの型を決定します。
ですから、public MyItem this[int index] のインデクサー宣言で、MyItem がないと、正しく型でコレクションエディタが開きません。

注意:
AddRange を実装しないと、正しくコレクションにアイテムを追加しません。
マニュアルにほとんど何も書かれてないので、試行錯誤です。


最後に、MyControl クラスを作成します。同様に、MyControl.cs を追加します。
ソリューションエクスプローラから、クラスライブラリを右クリック→追加→コンポーネントの追加を行い、
MyControl.cs を追加します。

Image
(今回は、Button から継承していますが、特に意味はありません。)
これがコレクションを持つクラスになります。フォームデザイナーでツールバーから部品を貼り付けるために、Windows.Control 以下の適当なクラスから継承します。

内部の実装は、ソースコードを参照してください。

注意:
#region Windows Form Designer generated code というように、Visual Studio が勝手に追加するコードがありますが、
その付近で、#region, #endregionを使うと #endregion がありませんというようなエラーになることがあります。
private void InitializeComponent() より前の自動生成されるコードの付近で、余分なことはやらないほうがいいです。

注意:
コンポーネントとしてツールバーに登録するには、DebugとRelease 両方でコンパイルする必要があります。(たぶん)^^l


注意:
ここで作ったMyItem は、スレッド・アンセーフです。

Step 4 MyControl をツールバーに貼り付ける。
まずは、ソリューションをビルドします。
次に、ツールボックスを右クリック→ツールボックスのカスタマイズを選択します。

Image

すると、次のようなツールボックスのカスタマイズダイアログが表示されます。

Image

そこで、.Net Framework コンポーネントタブを選択し、参照ボタンを押します。
ファイル選択用のダイアログが表示されますので、先ほどビルドしたクラスライブラリのアセンブリ(dll) を選択します。
これで、ツールボックスに次のような MyControl というコントロールが表示されたと思います。

Image

Step 5 MyControl を Form に貼り付ける。
さっそく、Windows.Forms にコントロールを貼り付けて、MyControl のプロパティを見てください。
次の図のように、Items プロパティが作成されていると思います。
このコレクションを ... ボタンで開くと、図のように MyItem コレクション エディタが開き、MyItem を追加することができます。

Image

では、いくつか MyItem のインスタンスを追加ボタンで追加してください。
このときに、インスタンスの区別がつくように TestString を適当に変更しておいてください。
Step 6 コレクションが正しく動くかテストしてみる。
では、Form にボタンを追加して、次のようなイベントハンドラを追加して、コレクションが正しく入力されたか確認してみましょう。

private void button1_Click(object sender, System.EventArgs e)
{
foreach(MyItem i in this.myPanel1.Items)
Console.WriteLine(i.TestString);
}

次のように、各コレクションの TestString が出力されればOKです。

Image

6.ソースコード(リソースを使う方法:お勧めできない) 


クラスライブラリ側ソースコード

注意:
この方法では、form1.cs の自動生成コードに、次のようなリソースから読み込む方法でコードが生成されます。

this.myControl1.Items = ((Uchukamen.Windows.TestCollection.MyCollection)(resources.GetObject("myControl1.Items")));

このため、コレクションの内容はリソースの状態に依存してしまうので、お勧めできません。
この方法では、Object から継承しているので、シリアライズが可能です。


MyItem.cs
using System;
using System.ComponentModel;

namespace Uchukamen.Windows.TestCollection
{
        [Serializable]    
        public class MyItem : Object 
        { 
                private string testString; 

                public MyItem() 
                { 
                        testString = "Hello World";
                } 

                public MyItem(string testString)
                {
                        this.testString = testString;
                }

                #region プロパティ
                [Category("MyItem")]
                public string TestString 
                { 
                        get 
                        { 
                                return testString; 
                        } 
                        set 
                        { 
                                testString = value; 
                        } 
                }  
                #endregion
        }
}
MyCollection.cs
using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;

namespace Uchukamen.Windows.TestCollection
{
        [Serializable]    
        public class MyCollection: System.Collections.ArrayList 
        {
                // フォームデザイナーでコレクションを編集する上で
                // コンストラクタを明示的に実装する必要があります。
                public MyCollection()
                {
                }

                /// <summary>
                /// CollectionEditor は、Item インデクサープロパティの返り値を
                /// リフレクションにより調べることによりコレクションの
                /// インスタンスの型を決定する。
                /// </summary>
                /// <注意>DesignerSerializationVisibility, Editor を指定しないと
                /// 正しく動作しない。
                /// </summary>
                [Category("MyItem")]
                [Description("MyItem の説明")]
                [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
                [Editor(typeof(System.ComponentModel.Design.CollectionEditor), typeof(System.Drawing.Design.UITypeEditor)) ]    

                public new MyItem this[int index]
                { 
                        get 
                        { 
                                return this[index];
                        }
                        set 
                        { 
                                this[index] = value;
                        }
                }

                public void AddRange(object [] items)
                {
                        base.AddRange(items);
                }
        }
}
MyControl.cs
using System;
using System.ComponentModel;
using System.Collections;
using System.Diagnostics;

namespace Uchukamen.Windows.TestCollection
{
        /// <summary>
        /// Component1 の概要の説明です。
        /// </summary>
        public class MyControl : System.Windows.Forms.PictureBox
        {
                /// <summary>
                /// 必要なデザイナ変数です。
                /// </summary>
                private System.ComponentModel.Container components = null;

                /// <summary>
                /// 
                /// </summary>
                /// <param name="container"></param>
                private MyCollection item = null;


                public MyControl(System.ComponentModel.IContainer container)
                {
                        /// <summary>
                        /// Windows.Forms クラス作成デザイナ サポートに必要です。
                        /// </summary>
                        container.Add(this);
                        InitializeComponent();

                        //
                        // TODO: InitializeComponent 呼び出しの後に、コンストラクタ コードを追加してください。
                        //
                }

                public MyControl()
                {
                        /// <summary>
                        /// Windows.Forms クラス作成デザイナ サポートに必要です。
                        /// </summary>
                        InitializeComponent();

                        //
                        // TODO: InitializeComponent 呼び出しの後に、コンストラクタ コードを追加してください。
                        //
                }

                #region Component Designer generated code
                /// <summary>
                /// デザイナ サポートに必要なメソッドです。このメソッドの内容を
                /// コード エディタで変更しないでください。
                /// </summary>
                private void InitializeComponent()
                {
                        components = new System.ComponentModel.Container();
                }
                #endregion

                #region コレクションエディタで Items を編集するためのコード

                [Category("MyCollection")]
                [Description("MyCollection の説明")]
                public MyCollection Items 
                { 
                        get 
                        { 
                                if (item == null) 
                                { 
                                        item = new MyCollection(); 
                                } 
                                return item; 
                        }
                        set
                        { 
                                item = value;
                        }
                }
                #endregion
        }
}

Windows.Forms 側ソースコード

Form1.cs
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using Uchukamen.Windows.TestCollection;

namespace CollectionItem
{
        /// <summary>
        /// Form1 の概要の説明です。
        /// </summary>
        public class Form1 : System.Windows.Forms.Form
        {
                private System.Windows.Forms.Button button1;
                private Uchukamen.Windows.TestCollection.MyControl myControl1;
                private System.ComponentModel.IContainer components;

                public Form1()
                {
                        //
                        // Windows フォーム デザイナ サポートに必要です。
                        //
                        InitializeComponent();

                        //
                        // TODO: InitializeComponent 呼び出しの後に、コンストラクタ コードを追加してください。
                        //
                }

                /// <summary>
                /// 使用されているリソースに後処理を実行します。
                /// </summary>
                protected override void Dispose( bool disposing )
                {
                        if( disposing )
                        {
                                if (components != null) 
                                {
                                        components.Dispose();
                                }
                        }
                        base.Dispose( disposing );
                }

                #region Windows Form Designer generated code
                /// <summary>
                /// デザイナ サポートに必要なメソッドです。このメソッドの内容を
                /// コード エディタで変更しないでください。
                /// </summary>
                private void InitializeComponent()
                {
                        this.components = new System.ComponentModel.Container();
                        System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(Form1));
                        this.button1 = new System.Windows.Forms.Button();
                        this.myControl1 = new Uchukamen.Windows.TestCollection.MyControl(this.components);
                        this.SuspendLayout();
                        // 
                        // button1
                        // 
                        this.button1.Location = new System.Drawing.Point(152, 160);
                        this.button1.Name = "button1";
                        this.button1.TabIndex = 1;
                        this.button1.Text = "button1";
                        this.button1.Click += new System.EventHandler(this.button1_Click);
                        // 
                        // myControl1
                        // 
                        this.myControl1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
                        this.myControl1.Items = ((Uchukamen.Windows.TestCollection.MyCollection)
                        (resources.GetObject("myControl1.Items")));
                        this.myControl1.Location = new System.Drawing.Point(56, 40);
                        this.myControl1.Name = "myControl1";
                        this.myControl1.Size = new System.Drawing.Size(104, 80);
                        this.myControl1.TabIndex = 2;
                        this.myControl1.TabStop = false;
                        // 
                        // Form1
                        // 
                        this.AutoScaleBaseSize = new System.Drawing.Size(5, 12);
                        this.ClientSize = new System.Drawing.Size(292, 266);
                        this.Controls.AddRange(new System.Windows.Forms.Control[] {
                        this.myControl1,
                        this.button1});
                        this.Name = "Form1";
                        this.Text = "Form1";
                        this.ResumeLayout(false);

                }
                #endregion

                /// <summary>
                /// アプリケーションのメイン エントリ ポイントです。
                /// </summary>
                [STAThread]
                static void Main() 
                {
                        Application.Run(new Form1());
                }

                private void button1_Click(object sender, System.EventArgs e)
                {
                        foreach(MyItem i in this.myControl1.Items)
                                Console.WriteLine(i.TestString);
                }
        }
}

7.ソースコード(リソースを使わないようにする方法:こちらがお勧め)


クラスライブラリ側ソースコード

注意:
この方法では、Item は System.ComponentModel.Component から継承する必要があります。
Component はシリアライズできないため、この方法では MyCollection をシリアライズできないという欠点があります。

注意:
この方法では、
this.myControl1.Items.AddRange(new Uchukamen.Windows.TestCollection.MyItem[] { this.myItem1, this.myItem2});
という形で、コードが自動生成されます。
このため、コードの移植性といった点で、こちらの方法が優れています。

注意:
参考文献(1)では、Component から継承するようにと書いてありますが、
そうするとフォームデザイナー上の部品欄にアイコンがたくさんできてしまいます。
そこで、MyItem.cs の中で
[DesignTimeVisible(false)]
というアトリビュートをつけて、フォームデザイナーに MyItem を表示しないようにしています。
このアトリビュートをはずせば、フォームデザイナーから直接 MyItem を編集できます。

MyItem.cs
using System;
using System.ComponentModel;

namespace Uchukamen.Windows.TestCollection
{
        [DesignTimeVisible(false)]
        public class MyItem : System.ComponentModel.Component
        { 
                private string testString; 

                public MyItem() 
                { 
                        testString = "Hello World";
                } 

                public MyItem(string testString)
                {
                        this.testString = testString;
                }

                public string TestString 
                { 
                        get 
                        { 
                                return testString; 
                        } 
                        set 
                        { 
                                testString = value; 
                        } 
                } 
        }
}
MyCollection.cs
using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;

namespace Uchukamen.Windows.TestCollection
{
        public class MyCollection: System.Collections.ArrayList 
        {
                // フォームデザイナーでコレクションを編集する上で
                // コンストラクタを明示的に実装する必要があります。
                public MyCollection()
                {
                }

                /// <summary>
                /// CollectionEditor は、Item インデクサープロパティの返り値を
                /// リフレクションにより調べることによりコレクションの
                /// インスタンスの型を決定する。
                /// </summary>  
                new public MyItem this[int index]
                { 
                        get 
                        { 
                                return this[index];
                        }
                        set 
                        { 
                                this[index] = value;
                        }
                }
                
                // AddRange を実装する必要がある。
                public void AddRange(MyItem[] obj)
                {
                        base.AddRange(obj);
                }
        }
}
MyControl.cs
using System.Drawing;
using System.ComponentModel;

namespace Uchukamen.Windows.TestCollection
{       /// <summary>
        /// Component1 の概要の説明です。
        /// </summary>
        public class MyControl : System.Windows.Forms.Button
        {
                /// <summary>
                /// 必要なデザイナ変数です。
                /// </summary>
                private System.ComponentModel.Container components = null;
                /// <summary>
                /// 
                /// </summary>
                /// <param name="container"></param>
                private MyCollection item = new MyCollection();

                public MyControl(System.ComponentModel.IContainer container)
                {
                        /// <summary>
                        /// Windows.Forms クラス作成デザイナ サポートに必要です。
                        /// </summary>
                        container.Add(this);
                        InitializeComponent();

                        //
                        // TODO: InitializeComponent 呼び出しの後に、コンストラクタ コードを追加してください。
                        //
                        
                        item = new MyCollection();
                }

                public MyControl()
                {
                        /// <summary>
                        /// Windows.Forms クラス作成デザイナ サポートに必要です。
                        /// </summary>
                        InitializeComponent();

                        //
                        // TODO: InitializeComponent 呼び出しの後に、コンストラクタ コードを追加してください。
                        //
                        
                        item = new MyCollection();
                }

                #region Component Designer generated code
                /// <summary>
                /// デザイナ サポートに必要なメソッドです。このメソッドの内容を
                /// コード エディタで変更しないでください。
                /// </summary>
                private void InitializeComponent()
                {
                        components = new System.ComponentModel.Container();
                }
                #endregion

                #region コレクションエディタで Items を編集するためのコード

                [Category("MyCollection")]
                [Description("MyCollection の説明")]
                [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
                public MyCollection Items 
                { 
                        get 
                        { 
                                return item; 
                        }
                        set
                        { 
                                item = value;
                        }
                }
                #endregion
        }
}

Windows.Forms 側ソースコード

Form1.cs
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using Uchukamen.Windows.TestCollection;

namespace CollectionItem
{
        /// <summary>
        /// Form1 の概要の説明です。
        /// </summary>
        public class Form1 : System.Windows.Forms.Form
        {
                private System.Windows.Forms.Button button1;
                private Uchukamen.Windows.TestCollection.MyControl myControl1;
                private Uchukamen.Windows.TestCollection.MyItem myItem1;
                private Uchukamen.Windows.TestCollection.MyItem myItem2;
                private System.ComponentModel.IContainer components;

                public Form1()
                {
                        //
                        // Windows フォーム デザイナ サポートに必要です。
                        //
                        InitializeComponent();

                        //
                        // TODO: InitializeComponent 呼び出しの後に、コンストラクタ コードを追加してください。
                        //
                }

                /// <summary>
                /// 使用されているリソースに後処理を実行します。
                /// </summary>
                protected override void Dispose( bool disposing )
                {
                        if( disposing )
                        {
                                if (components != null) 
                                {
                                        components.Dispose();
                                }
                        }
                        base.Dispose( disposing );
                }

                #region Windows Form Designer generated code
                /// <summary>
                /// デザイナ サポートに必要なメソッドです。このメソッドの内容を
                /// コード エディタで変更しないでください。
                /// </summary>
                private void InitializeComponent()
                {
                        this.components = new System.ComponentModel.Container();
                        this.button1 = new System.Windows.Forms.Button();
                        this.myControl1 = new Uchukamen.Windows.TestCollection.MyControl(this.components);
                        this.myItem1 = new Uchukamen.Windows.TestCollection.MyItem();
                        this.myItem2 = new Uchukamen.Windows.TestCollection.MyItem();
                        this.SuspendLayout();
                        // 
                        // button1
                        // 
                        this.button1.Location = new System.Drawing.Point(152, 160);
                        this.button1.Name = "button1";
                        this.button1.TabIndex = 1;
                        this.button1.Text = "button1";
                        this.button1.Click += new System.EventHandler(this.button1_Click);
                        // 
                        // myControl1
                        // 
                        this.myControl1.Items.AddRange(new Uchukamen.Windows.TestCollection.MyItem[] {                                                                              
                              this.myItem1, this.myItem2});
                        this.myControl1.Location = new System.Drawing.Point(48, 32);
                        this.myControl1.Name = "myControl1";
                        this.myControl1.TabIndex = 4;
                        this.myControl1.Text = "myControl1";
                        // 
                        // myItem1
                        // 
                        this.myItem1.TestString = "Hello World";
                        // 
                        // myItem2
                        // 
                        this.myItem2.TestString = "Hello World";
                        // 
                        // Form1
                        // 
                        this.AutoScaleBaseSize = new System.Drawing.Size(5, 12);
                        this.ClientSize = new System.Drawing.Size(292, 266);
                        this.Controls.AddRange(new System.Windows.Forms.Control[] {
                                this.myControl1,  his.button1});
                        this.Name = "Form1";
                        this.Text = "Form1";
                        this.ResumeLayout(false);

                }
                #endregion

                /// <summary>
                /// アプリケーションのメイン エントリ ポイントです。
                /// </summary>
                [STAThread]
                static void Main() 
                {
                        Application.Run(new Form1());
                }

                private void button1_Click(object sender, System.EventArgs e)
                {
                        foreach(MyItem i in this.myControl1.Items)
                                Console.WriteLine(i.TestString);
                }
        }
}