none
DataGridViewにIListをメンバープロパティとして持つIList実装クラスを行・列にバインドさせたい RRS feed

  • 質問

  • いつも参考にさせていただいております。
    DataGridView のDataSourceにIListを実装するクラスを指定すると、そのアイテムコレクションが行になることは、確認しております。
    そこで、その一つ一つの行にあてがわれたオブジェクトのリストプロパティを各列にバインドしたいのです。
    オブジェクトのサンプルとしては、以下のようなものです。

    class 項目
    {
       public string 項目名 { get; set; }
       public string 値
    { get; set; }
    }

    class 項目コレクション : ObservableCollection<項目>
    {
       public this[string 項目名] { get { return (from i in this where i.名 == 項目名 select i).FirstOrDefault(); } }
    }

    class 対象
    {
       public 対象() { 項目群 = new
    項目コレクション; }
       public string 対象名称
    { get; set; }
       public 項目コレクション 項目群 { get; protected set; }
    }

    この、「対象」オブジェクトをデータバインドして、表に反映させたいのですが、無理なのでしょうか?
    実際には、INotifyPropertyChangedも実装してあります。

    よろしくお願いいたします。
    2009年1月26日 4:42

回答

  • なるほど、見えてきました。
    さてそうなると結構面倒ですね。
    まず 対象クラスには IListSource を実装させて、DataGridView に表示させるためのリストを返すように実装します。
    問題はこの「DataGridView に表示させるためのリスト」ですが。
    通常 DataGridView は、バインドされているリストに格納されているオブジェクトの、プロパティを列に表示します。ここで列をプロパティ以外にしたいのなら、ITypedList を実装する必要があります。もちろん、IListSource.GetList は IList を返すので、ITypedList と同時に IList も実装しなければなりません。
    ITypedList で列自体は比較的簡単に作成可能ですが、問題は行ですね。
    DataGridView は一行が一つのオブジェクトですが、ご希望の動作では逆に一列が一つのオブジェクトということになります。そのままではどうにもなりませんから、結局、基コレクションのピボットなビューを作る必要があります。
    ざっくりこんな感じでしょうか。

    Code Snippet

    class 項目PivotRow {
      項目PivotRow(項目コレクション) {
        this.columns = 項目コレクション;
      }
      項目コレクション columns;
      public string this[string name] {
        get { return this.columns[name]; }
        set { this.columns[name] = value; }
      }
    }
    class 項目PivotView : IList, ITypedList {
      // 項目に項目名と値しかないのなら行は1つだけになる
      private 項目PivotRow row;
      private 項目コレクション columns;
      public 項目PivotView(項目コレクション) {
        this.row = new 項目PivotRow(項目コレクション);
        this.columns = 項目コレクション;
      }
      // ITypedList.GetItemProperties
      public PropertyDescriptorCollection GetItemProperties(...) {
        ColumnDescriptor[] descriptors = new ColumnDescriptor[this.columns.Count];
        for (int i = 0; i < descriptors.Length; ++i) {
          descriptors[i] = new 項目Descriptors(this.columns[i].項目名);
        }
        return new PropertyDescriptorCollection(descriptors);
      }
      // IList.GetEnumerator
      public IEnumerator GetEnumerator() {
        yield return this.row;
      }
    }
    class 項目Descriptor : TypeDescriptor {
      public 項目Descriptor(string name) : base(name) {
      }
      public override object GetValue(object component) {
        return ((項目PivotRow)component)[this.Name];
      }
      public override object SetValue(object component, object value) {
        ((項目PivotRow)component)[this.Name] = (string)value;
      }
    }
    class 対象 : IListSource {
      // IListSource.GetList
      public IList GetList() {
        return new 項目PivotView(this.項目群);
      }
    }

     

     

    項目群に変更があったときの対応などを考えるともっと色々必要になるでしょうが、基本はこんな感じですね(インターフェイスの実装等、コンパイルに必要な部分も省略してますが)。列の変更を DataGridView に通知するには IList ではなく IBindingList が必要かもしれません。

    この辺はデータバインディングの結構コアな部分で、上に挙げたインターフェイスもバインディングに利用されるインターフェイスのうちの一部に過ぎません。Windows フォームでのデータ バインディング 以下を一読することをお勧めします。

    • 回答としてマーク sk7474 2009年2月12日 5:18
    2009年1月26日 12:10

すべての返信

  • 微妙によく分かってませんが、対象クラスに IListSource を実装させればいいんじゃないですか?
    2009年1月26日 8:23
  • HongLiangさん、こんにちは。回答ありがとうございます。

    IListSourceで解決できるのでしょうか。私の説明がまずいのだと思うので、もう一度、説明させてください。
    DataGridViewColumnのDataPropertyNameには、プロパティ名が設定できますが、そこに "項目群" とすると、DataGridViewの本体に、「(コレクション)」と表示されます。
    そのコレクションの中身を列として展開したいのです。
    ReflectionのGetPropertiesで取得できるPropertyInfo.Nameにしか対応していないようなので、インデクサーに対応して"項目群[0]"とかやっても、値がもらえませんでした。

    つまり、
    ヘッダーが[
    「項目群[0].項目名」の値][「項目群[1].項目名」の値]……
    セルが、[「項目群[0].値」の値]
    [「項目群[1].値」の値]……
    となってほしいのです。
    2009年1月26日 9:10
  • なるほど、見えてきました。
    さてそうなると結構面倒ですね。
    まず 対象クラスには IListSource を実装させて、DataGridView に表示させるためのリストを返すように実装します。
    問題はこの「DataGridView に表示させるためのリスト」ですが。
    通常 DataGridView は、バインドされているリストに格納されているオブジェクトの、プロパティを列に表示します。ここで列をプロパティ以外にしたいのなら、ITypedList を実装する必要があります。もちろん、IListSource.GetList は IList を返すので、ITypedList と同時に IList も実装しなければなりません。
    ITypedList で列自体は比較的簡単に作成可能ですが、問題は行ですね。
    DataGridView は一行が一つのオブジェクトですが、ご希望の動作では逆に一列が一つのオブジェクトということになります。そのままではどうにもなりませんから、結局、基コレクションのピボットなビューを作る必要があります。
    ざっくりこんな感じでしょうか。

    Code Snippet

    class 項目PivotRow {
      項目PivotRow(項目コレクション) {
        this.columns = 項目コレクション;
      }
      項目コレクション columns;
      public string this[string name] {
        get { return this.columns[name]; }
        set { this.columns[name] = value; }
      }
    }
    class 項目PivotView : IList, ITypedList {
      // 項目に項目名と値しかないのなら行は1つだけになる
      private 項目PivotRow row;
      private 項目コレクション columns;
      public 項目PivotView(項目コレクション) {
        this.row = new 項目PivotRow(項目コレクション);
        this.columns = 項目コレクション;
      }
      // ITypedList.GetItemProperties
      public PropertyDescriptorCollection GetItemProperties(...) {
        ColumnDescriptor[] descriptors = new ColumnDescriptor[this.columns.Count];
        for (int i = 0; i < descriptors.Length; ++i) {
          descriptors[i] = new 項目Descriptors(this.columns[i].項目名);
        }
        return new PropertyDescriptorCollection(descriptors);
      }
      // IList.GetEnumerator
      public IEnumerator GetEnumerator() {
        yield return this.row;
      }
    }
    class 項目Descriptor : TypeDescriptor {
      public 項目Descriptor(string name) : base(name) {
      }
      public override object GetValue(object component) {
        return ((項目PivotRow)component)[this.Name];
      }
      public override object SetValue(object component, object value) {
        ((項目PivotRow)component)[this.Name] = (string)value;
      }
    }
    class 対象 : IListSource {
      // IListSource.GetList
      public IList GetList() {
        return new 項目PivotView(this.項目群);
      }
    }

     

     

    項目群に変更があったときの対応などを考えるともっと色々必要になるでしょうが、基本はこんな感じですね(インターフェイスの実装等、コンパイルに必要な部分も省略してますが)。列の変更を DataGridView に通知するには IList ではなく IBindingList が必要かもしれません。

    この辺はデータバインディングの結構コアな部分で、上に挙げたインターフェイスもバインディングに利用されるインターフェイスのうちの一部に過ぎません。Windows フォームでのデータ バインディング 以下を一読することをお勧めします。

    • 回答としてマーク sk7474 2009年2月12日 5:18
    2009年1月26日 12:10
  •  こんばんは!(^^)!ふ~です。

    データ バインディングの概要

    http://msdn.microsoft.com/ja-jp/library/ms752347(VS.80).aspx


    面白いテーマ―ですね。私も、データーバインディングの技術は進歩していると思います。
    少しは勉強で、IListでも、やってみようと始めました。

    <IListクラスのメソッド>
       名前    説明 
       Add   IList に項目を追加します。  
       Clear   IList からすべての項目を削除します。  
       Contains   IList に特定の値が格納されているかどうかを判断します。  
       IndexOf   IList 内での指定した項目のインデックスを調べます。  
       Insert   指定したインデックスの IList に項目を挿入します。  
       Remove   IList 内で最初に見つかった特定のオブジェクトを削除します。  
       RemoveAt   指定したインデックスにある IList 項目を削除します。 

    取り合えずIListを実装するクラスと思ったのですが、上記7つのメッソッドの他にも実装する必要が有ります。そこで、Add()だけを作って見ました。

    最も単純に考え、『複数列×1行』の列ごとの追加とその読取りです。

    // ファイル名 Form1.cs  
    using System;  
    using System.Windows.Forms;  
    using System.Diagnostics;  
     
    namespace DataGridViewIList  
    {  
        public partial class Form1 : Form  
        {  
            // dataGridViewをバインドするクラスを宣言する  
            DGViewIList dgvList;  
     
            public Form1()  
            {  
                InitializeComponent();  
     
                // DGViewIListクラスのインスタンスを生成する。  
                dgvList = new DGViewIList( this.dataGridView1 );   
            }  
     
            private void Form1_Load(object sender, EventArgs e)  
            {  
                ////////////////////////////  
                // 複数列×1行を作成する //  
                ////////////////////////////  
                  
                dgvList.Add("System.String", "名前", "たま");  
                dgvList.Add("System.Int32", "年齢", "10");  
                dgvList.Add("System.String", "趣味", "サッカー");  
     
                //////////////////  
                // 値の読出処理 //  
                //////////////////  
     
                // テーブル全てを読出す。  
                foreach ( TableObj tbl in dgvList )  
                {  
                    if ( tbl.Row is string)  
                    {  
                        Debug.WriteLine(tbl.Column + "=" + tbl.Row);   
                    }  
                    else  
                    {  
                        Debug.WriteLine( tbl.Column + "=" + tbl.Row.ToString());   
                    }  
                }  
       
                ///////////////////////////////////////////////  
                // 各列の更新をする(データタイプの変更不可) //  
                ///////////////////////////////////////////////  
                  
                TableObj objVal1 = new TableObj( "氏名", "太郎" );  
                dgvList[2] = objVal1;  
     
                //////////////////////  
                // 各列の読出をする //  
                //////////////////////  
     
                TableObj objVal2 = new TableObj();  
                objVal2 = (TableObj)dgvList[0];  
                Debug.WriteLine(objVal2.Column + "=" + objVal2.Row);  
     
                TableObj objVal3 = new TableObj();  
                objVal3 = (TableObj)dgvList[1];  
                Debug.WriteLine(objVal3.Column + "=" + objVal3.Row.ToString());   
     
     
                TableObj objVal4 = new TableObj();  
                objVal2 = (TableObj)dgvList[2];  
                Debug.WriteLine(objVal2.Column + "=" + objVal2.Row);   
            }  
        }  
    }  
     
    // ファイル名 DGViewIList.cs  
    using System;  
    using System.Windows.Forms;     // DataGridView  
    using System.Collections;       // IList  
    using System.Data;              // DataTable  
    using System.Diagnostics;       // Debug   
     
    namespace DataGridViewIList  
    {  
        class DGViewIList : IEnumerable  
        {  
            // テーブル関連の宣言  
            DataTable table;  
     
            // 初期化処理を行う  
            public DGViewIList(DataGridView dgv)  
            {  
                // データテーブルを生成する  
                table = new DataTable("DGViewTable");  
     
                // DataGredViewのデーターソースを設定する  
                dgv.DataSource = table;  
     
            }  
     
            /// <summary> 
            /// 『複数列×1行の表』の  
            /// 列タイトルと行値を追加する  
            /// </summary> 
            /// <param name="DataType">System.Stringなどのフルネーム</param> 
            /// <param name="ColumnName">列のタイトル名</param> 
            /// <param name="RowName">1行目の値</param> 
            public void Add(string DataType, string ColumnName, string RowName)  
            {  
                DataColumn column = new DataColumn();  
                column.DataType = System.Type.GetType(DataType);  
                column.ColumnName = ColumnName;  
                column.AutoIncrement = false;  
                column.Caption = ColumnName;  
                column.ReadOnly = false;  
                column.Unique = false;  
                table.Columns.Add(column);  
     
                // 1行目があるか?  
                if (table.Rows.Count == 0)  
                {  
                    DataRow row = table.NewRow();  
                    table.Rows.Add(row);  
                }  
     
                // 列のタイプに従って変換する  
                switch (table.Columns[table.Columns.Count - 1].DataType.FullName)  
                {  
                    case "System.Int32":  
                        table.Rows[0][table.Columns.Count - 1] = int.Parse(RowName);  
                        break;  
     
                    case "System.String":  
                        table.Rows[0][table.Columns.Count - 1] = RowName;  
                        break;  
     
                    default:  
                        Debug.WriteLine("変換する方法を作成して下さい。");  
                        break;  
                }  
            }  
     
            /// <summary> 
            /// インデックスで指定する列の行値を取得または、変更する  
            /// </summary> 
            /// <param name="index"></param> 
            /// <returns></returns>  
            public object this[int index]  
            {  
                get  
                {  
                    TableObj tbl = new TableObj  
                    ( table.Columns[index],  
                      table.Rows[0][index] );  
                    return tbl;  
                }  
                set  
                {  
                    TableObj tbl = new TableObj();  
                    tbl = (TableObj)value;  
                    table.Rows[0][index] = (object)tbl.Row;  
                    table.Columns[index].ColumnName = (string)tbl.Column;  
                }  
            }  
     
            /// <summary> 
            /// foreach()をでデーブルを出力する(ラッパ)  
            /// </summary> 
            /// <returns></returns>  
            public IEnumerator GetEnumerator()  
            {  
                return new DGVIList(table);  
            }  
        }  
     
        /// <summary> 
        /// テーブルの列タイトルと行値を格納する  
        /// </summary> 
        public class TableObj  
        {  
            public TableObj(){}  
            public TableObj( object objCol, object objRow )  
            {  
                this.Column = objCol;  
                this.Row = objRow;  
            }  
            public object Column;  
            public object Row;  
        }  
     
        /// <summary> 
        /// foreach()をでデーブルを出力する処理  
        /// </summary> 
        public class DGVIList : IEnumerator  
        {  
            public DataTable _dgvList;  
     
            int position = -1;  
     
            public DGVIList(DataTable list)  
            {  
                _dgvList = list;  
            }  
     
            public bool MoveNext()  
            {  
                position++;  
                return (position < _dgvList.Columns.Count);  
            }  
     
            public void Reset()  
            {  
                position = -1;  
            }  
     
            public object Current  
            {  
                get  
                {  
                    try  
                    {  
                        TableObj tbl = new TableObj  
                        ( _dgvList.Columns[position],  
                          _dgvList.Rows[0][position] );  
                        return tbl;  
                    }  
                    catch (IndexOutOfRangeException)  
                    {  
                        throw new InvalidOperationException();  
                    }  
                }  
            }  
        }  
    }  
     
     
    <作成方法>
    1.Form1に、dataGridView1を貼り付けます。
    2.自作のDataGridViewIListクラスをプロジェクトに追加します。
    IListクラスを継承させるつもりでしたが、サイズが大きくなりそうでしたので、変わりに『IEnumerableインターフェイス』を加えてあります。
    先は、遠いですが、先ずは簡単な所から話題は無いでしょうか?
    以上
    2009年2月11日 12:48
  • こんにちは。中川俊輔です。

    Hongliangさん、ふ~さん、回答ありがとうございます。

    尾画茶さん、フォーラムのご利用ありがとうございます。
    その後いかがでしょうか?
    有用な情報と思われたため、Hongliangさんの回答へ回答済みチェックをつけさせていただきました。

    今後ともフォーラムをよろしくお願いします。
    それでは!
    マイクロソフト株式会社 フォーラム オペレータ 中川 俊輔
    2009年2月12日 5:30
  • こんにちは!(^^)!ふ~です。
    投稿して18時間で、締め出されてしまいました。悲しいなぁ~。
    基本からと思ったのですが内容が薄かったでしょうか?。。。。。。。。。。.......
    今後の人生の参考にしたいと思います。ご自由に、ご発言頂ければ幸いです。
    2009年2月12日 7:05