none
DataGridViewのBindingについて RRS feed

  • 質問

  • 下記サイトを参考にDataGridViewのコレクションデータバインドを勉強してますが、View→Modelのバインドはデザイナからデータソースを登録して同期されるのは理解しましたが、View←Modelはどのように同期するのでしょうか?

    https://blogs.msdn.microsoft.com/nakama/2009/02/26/part-1-2/

    思いつく手法としては、Model側でDataSet、DataTableなどを作成してView側に渡して、BindingしていたDataSourceを入れ替えるなど考えましたが、これだとあまりバインドの意味を成さないような気がして質問させて頂きました。


    2017年4月14日 17:22

回答

  • デザイン時に DataGridView の列に対して、DataPropertyName として「Title」や「ID」が指定されていて、かつ、bindingSource1 が DataGridView の DataSource に割り当てられている場合は、提示頂いたコードで DataGridView に 4 つの行が表示されますね。

    今回はそのように設定されているという前提で回答します。

    > イベント発生時(button_Click)にDataSourceを更新したい場合、

    dataGridView1.DataSource にバインドされているのは BindingSource なので、たとえば行を追加する場合には、直接 _Params.Add を行うのではなく、
     bindingSource1.Add(new Param() { Title = "NEW", ID = 5 });
    のように、BindingSource を通じて行うのが望ましいです。

    直接 _Params.Add しても良いのですが、その場合、リストの件数が変化したことがDataGridView に通知されないので、Add 後は直ちに bindingSource1.ResetBindings(false); を呼び出して表示値を更新するようにします。

    ただし、_Params 自身が変更通知の仕組みを備えている場合はこの限りではありません。

    たとえば『private List<Param> _Params;』ではなく『private BindingList<Param> _Params;』であった場合には、bindingSource1.Add と _Params.Add のいずれであっても、件数の変化が即座に DataGridView に反映されるため、ResetBindings の呼び出しが不要となります。

    データの編集時も同様です。バインド中に値が変更された場合、そのままでは表示値が更新さませんので、bindingSource1.ResetCurrentItem(); あるいは ResetItem などの呼び出しが必要なのですが、変更通知が可能な DataSource (たとえば DataTable) の場合には、ResetItem 等を呼び出さずとも、Title や ID の変更結果が DataGridView に反映されます。

    2017年4月15日 18:06
  • >     public class Param : ErrorProvider

    ErrorProvider を継承したクラスを作りたいのですか?

    やりたいことがよくわかりませんが、ID に負数が入力されたときにエラーアイコンを表示したいのであれば、下記のように記述できます。

    DataTable table;
    private void Form1_Load(object sender, EventArgs e)
    {
        table = new DataTable("table");
        table.Columns.Add("Title");
        table.Columns.Add("ID", typeof(int));
        table.Rows.Add("data1", 1);
        table.Rows.Add("data2", 2);
        table.Rows.Add("data3", 3);
        table.Rows.Add("data4", 4);
        table.AcceptChanges();
    
        dataGridView1.Columns.Clear();
        dataGridView1.AutoGenerateColumns = true;
        dataGridView1.DataSource = table;
    
        dataGridView1.CellErrorTextNeeded += DataGridView1_CellErrorTextNeeded;
    }
    
    private void DataGridView1_CellErrorTextNeeded(object sender, DataGridViewCellErrorTextNeededEventArgs e)
    {
        const int COL_ID = 1;
        if(e.ColumnIndex == COL_ID)
        {
            DataGridView dgv = (DataGridView)sender;
            object cellVal = dgv[e.ColumnIndex, e.RowIndex].Value;
            if (cellVal is int && ((int)cellVal) < 0)
            {
                e.ErrorText = "負の整数は入力できません。";
            }
        }
    }

    2017年4月15日 19:59

すべての返信

  • 何を作っているのでしょう? 開発環境は?(最初に書いてくださいね)

    DataGridView という言葉が出てくるということは Windows Forms アプリですか? Visual Studio は 2008 以降という理解でいいですか?

    その理解でよければ、一度 Visual Studio のウィザードを使って定番の構成のアプリを作ってみてはいかがでしょう?

    質問者さんの使っている DB でウィザードが使えるかどうか分かりませんが、使えなければ SQL Server Express を使ってとりあえず作ってみて、ウィザードが自動生成するコードを見れば今後の開発の参考になると思います。
     
    DB が SQL Server の場合ですが、以下のチュートリアル、
     
    チュートリアル : データベースへのデータの保存 (単一テーブル)
    https://msdn.microsoft.com/ja-jp/library/0f92s97z(v=vs.120).aspx
     
    10 行でズバリ !! 非接続型のデータ アクセス (ADO.NET) (C#)
    https://code.msdn.microsoft.com/windowsdesktop/10-ADONET-C-cbfe7688
     
    ・・・のように Visual Studio のデータソース構成ウィザードを利用して型付 DataSet + TableAdapter を作って、それを利用してアプリを作ると、以下のページの図のような構造のアプリが、ほとんど自分でコードを書くこと無しに作れます。(見ての通り Model, View は使いません)
     
    Windows フォーム アプリケーションでのデータへの接続
    https://msdn.microsoft.com/ja-jp/library/wxt2cwcc(v=vs.120).aspx

    操作に慣れると 10 分もかからず作れるはずです。開発工数は激減、保守工数も減るはずです。お試しください。

    2017年4月14日 23:08
  • 申し訳ありません。開発環境はVisual Studio 2015 communityで、プロジェクトはWinFormです。

    また、参考URLも誤っておりました。。

    http://dixq.net/forum/viewtopic.php?f=3&t=11426

    上記サイトのサンプルコードを参考に、DataGridViewを使用してDataBaseではなくテキストやCSVファイルから読みだしたデータを一覧表示させたいと考えておりました。

    2017年4月15日 0:43
  • > また、参考URLも誤っておりました。。
    >
    > http://dixq.net/forum/viewtopic.php?f=3&t=11426
    >
    > 上記サイトのサンプルコードを参考に、DataGridViewを使用してDataBaseではなくテキストや
    > CSVファイルから読みだしたデータを一覧表示させたいと考えておりました。

    その URL も間違ってませんか? その URL の質問は、Form1 に配置した TextBox の Text プロパティの値を Form2 で取得したいということのようですが、それとこのスレッドの掲題「DataGridViewのBindingについて」や上のレスの「ファイルから読みだし」とどういう関係があるのですか?

    他のサイトの記事で何ですが、以下の URL の記事の「2. 質問をする前に自分で何がわからないのかを把握しましょう」と「3. 正しく伝わる質問の仕方」のセクションを読んで、質問内容を見直していただけませんか?

    質問するときのヒント
    https://teratail.com/help/question-tips

    上の記事にも書いてありますが "何がわからないかわからない人から質問を受けても、答える側も困ってしまいます" という状態です。

    2017年4月15日 5:50
  • 質問がわかりづらく申し訳ありません。

        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            private List<Param> _Params;
    
            private void Form1_Load(object sender, EventArgs e)
            {
                _Params = new List<Param>();
                _Params.Add(new Param() { Title = "data1", ID = 1 });
                _Params.Add(new Param() { Title = "data2", ID = 2 });
                _Params.Add(new Param() { Title = "data3", ID = 3 });
                _Params.Add(new Param() { Title = "data4", ID = 4 });
    
                this.bindingSource1.DataSource = _Params;
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                // テキストやCSVからデータを抽出する
    
                // 抽出したデータを_ParamsにAddする?
            }
        }
    
        public class Param
        {
            private string _Title;
            public string Title
            {
                set { _Title = value; }
                get { return _Title; }
            }
    
            private int _ID;
            public int ID
            {
                set { _ID = value; }
                get { return _ID; }
            }
        }
    

    上記のようなサンプルを作成しました。(bindingSource1のDataSourceにParamを設定済み)

    Load時に仮データとしてParamのリストをDataSourceに設定しておりますが、イベント発生時(button_Click)にDataSourceを更新したい場合、直接_Paramを操作する方法が正しいのでしょうか?

    上記のサンプルではErrorProviderをまだ設定しておりませんが、UI側から操作によるチェックはErrorProviderでチェックするとして、イベント発生時にDataSourceを更新する場合、_ParamをAddする際に個別でチェックするしかないでしょうか?

    2017年4月15日 12:17
  • 私にはあなたが期待する対応は無理なようです。すみませんが他の方の回答をお待ちください。
    2017年4月15日 13:51
  • デザイン時に DataGridView の列に対して、DataPropertyName として「Title」や「ID」が指定されていて、かつ、bindingSource1 が DataGridView の DataSource に割り当てられている場合は、提示頂いたコードで DataGridView に 4 つの行が表示されますね。

    今回はそのように設定されているという前提で回答します。

    > イベント発生時(button_Click)にDataSourceを更新したい場合、

    dataGridView1.DataSource にバインドされているのは BindingSource なので、たとえば行を追加する場合には、直接 _Params.Add を行うのではなく、
     bindingSource1.Add(new Param() { Title = "NEW", ID = 5 });
    のように、BindingSource を通じて行うのが望ましいです。

    直接 _Params.Add しても良いのですが、その場合、リストの件数が変化したことがDataGridView に通知されないので、Add 後は直ちに bindingSource1.ResetBindings(false); を呼び出して表示値を更新するようにします。

    ただし、_Params 自身が変更通知の仕組みを備えている場合はこの限りではありません。

    たとえば『private List<Param> _Params;』ではなく『private BindingList<Param> _Params;』であった場合には、bindingSource1.Add と _Params.Add のいずれであっても、件数の変化が即座に DataGridView に反映されるため、ResetBindings の呼び出しが不要となります。

    データの編集時も同様です。バインド中に値が変更された場合、そのままでは表示値が更新さませんので、bindingSource1.ResetCurrentItem(); あるいは ResetItem などの呼び出しが必要なのですが、変更通知が可能な DataSource (たとえば DataTable) の場合には、ResetItem 等を呼び出さずとも、Title や ID の変更結果が DataGridView に反映されます。

    2017年4月15日 18:06
  • ご回答ありがとうございます。データをセットして更新させることができました。

    Rowに対して即時反映したい場合はBindingListやDataTable、まとめて更新する場合はBindingSource.ResetBindings(false)という認識で正しいでしょうか?

        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            private BindingList<Param> _Params;
    
            private void Form1_Load(object sender, EventArgs e)
            {
                _Params = new BindingList<Param>();
                _Params.Add(new Param() { Title = "data1", ID = 1 });
                _Params.Add(new Param() { Title = "data2", ID = 2 });
                _Params.Add(new Param() { Title = "data3", ID = 3 });
                _Params.Add(new Param() { Title = "data4", ID = 4 });
    
                this.bindingSource1.DataSource = _Params;
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                _Params.Clear();
                _Params.Add(new Param() { Title = "data0", ID = 0 });
            }
        }
    

    また、Add時のエラーチェックについて以下の方法で実装しましたが、setでエラーをセットしていることは確認できましたが、アイコンが表示されないため何か誤りがあるのでしょうか?

    #ErrorProviderのDataSourceにbindingSource1を設定済みです

        public class Param : ErrorProvider
        {
            protected Dictionary<string, string> Errors;
    
            private string _Title;
            public string Title
            {
                set { _Title = value; }
                get { return _Title; }
            }
    
            private int _ID;
            public int ID
            {
                set
                {
                    _ID = value;
                    if (_ID <= 0)
                    {
                        Errors["ID"] = "IDは0以上です";
                    }
                    else
                    {
                        Errors.Remove("ID");
                    }
                }
                get { return _ID; }
            }
    
            public string this[string columnName]
            {
                get
                {
                    string result;
                    if (Errors.TryGetValue(columnName, out result))
                    {
                        return result;
                    }
    
                    return null;
                }
            }
    
            public string Error
            {
                get
                {
                    return Errors.Count == 0 ? null : string.Join(Environment.NewLine, Errors.Select(p => string.Format("{0} : {1}", p.Key, p.Value)).ToArray());
                }
            }
    
            public Param()
            {
                Errors = new Dictionary<string, string>();
            }
        }
    


    2017年4月15日 19:30
  • >     public class Param : ErrorProvider

    ErrorProvider を継承したクラスを作りたいのですか?

    やりたいことがよくわかりませんが、ID に負数が入力されたときにエラーアイコンを表示したいのであれば、下記のように記述できます。

    DataTable table;
    private void Form1_Load(object sender, EventArgs e)
    {
        table = new DataTable("table");
        table.Columns.Add("Title");
        table.Columns.Add("ID", typeof(int));
        table.Rows.Add("data1", 1);
        table.Rows.Add("data2", 2);
        table.Rows.Add("data3", 3);
        table.Rows.Add("data4", 4);
        table.AcceptChanges();
    
        dataGridView1.Columns.Clear();
        dataGridView1.AutoGenerateColumns = true;
        dataGridView1.DataSource = table;
    
        dataGridView1.CellErrorTextNeeded += DataGridView1_CellErrorTextNeeded;
    }
    
    private void DataGridView1_CellErrorTextNeeded(object sender, DataGridViewCellErrorTextNeededEventArgs e)
    {
        const int COL_ID = 1;
        if(e.ColumnIndex == COL_ID)
        {
            DataGridView dgv = (DataGridView)sender;
            object cellVal = dgv[e.ColumnIndex, e.RowIndex].Value;
            if (cellVal is int && ((int)cellVal) < 0)
            {
                e.ErrorText = "負の整数は入力できません。";
            }
        }
    }

    2017年4月15日 19:59
  • >     public class Param : ErrorProvider

    ErrorProvider を継承したクラスを作りたいのですか?

    ご指摘ありがとうございます。。IDataErrorInfoと誤っておりました。

    UI側とデータセット側の双方からデータチェックを行いたく、IDataErrorInfoに変更することで可能となりました。

    上記でやりたいことが実現できました。ありがとうございました。

    2017年4月15日 20:08