C# dataGridViewのコツ


### １．高速化の定石
Form_Loadなどで。
DataGridViewの画面ちらつきをおさえ高速表示するために、DoubleBufferedを有効にする。

```C#
// DataGirdViewのTypeを取得
System.Type dgvtype = typeof(DataGridView);
// プロパティ設定の取得
System.Reflection.PropertyInfo dgvPropertyInfo = dgvtype.GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
// 対象のDataGridViewにtrueをセットする
dgvPropertyInfo.SetValue(dataGridView1, true, null);

```
### ２．データテーブルのセット～書式設定
```C#
//件数確保
int 件数 = dt.Rows.Count;
label読込行数.Text = 件数.ToString() + " 行";

//dataGridViewにデータテーブルをセットする。
dataGridView1.DataSource = dt;

//dataGridViewの設定
//ユーザの操作規制　←これはFormのLoadへ持って行っても良い
dataGridView1.ReadOnly = true;                      //読取専用
dataGridView1.AllowUserToDeleteRows = false;        //行削除禁止
dataGridView1.AllowUserToAddRows = false;           //行挿入禁止
dataGridView1.AllowUserToResizeRows = false;        //行の高さ変更禁止
dataGridView1.RowHeadersVisible = false;            //行ヘッダーを非表示にする
dataGridView1.MultiSelect = false;                  //セル、行、列が複数選択禁止
dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;       //セルを選択すると行全体が選択されるようにする

//並び替えができないようにする　【注意】データがセットされた後でしか効かないよ。
foreach (DataGridViewColumn c in dataGridView1.Columns)
    c.SortMode = DataGridViewColumnSortMode.NotSortable;

//コピーモード
dataGridView1.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableAlwaysIncludeHeaderText;

//基本フォントの設定
dataGridView1.DefaultCellStyle.Font = new System.Drawing.Font("MS UI Gothic", 10);

//ヘッダーの色等
dataGridView1.EnableHeadersVisualStyles = false;     // Visualスタイルを使用しない
dataGridView1.ColumnHeadersDefaultCellStyle.BackColor = Color.LightGray;
dataGridView1.ColumnHeadersDefaultCellStyle.Font = new System.Drawing.Font("MS UI Gothic", 9);
dataGridView1.ColumnHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
//ヘッダ高さ
dataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.EnableResizing;
dataGridView1.ColumnHeadersHeight = 30;

//ヘッダー名称変更
dataGridView1.Columns["住所1"].HeaderCell.Value = "県名";

//カラム幅設定
dataGridView1.Columns[0].Width = 50;     //社員番号
dataGridView1.Columns[1].Width = 100;    //カラム名
dataGridView1.Columns[2].Width = 70;     //カラム名
dataGridView1.Columns[3].Width = 70;     //カラム名
dataGridView1.Columns[4].Width = 70;     //カラム名
dataGridView1.Columns[5].Width = 80;     //カラム名

//寄せ
dataGridView1.Columns["社員番号"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;

//数値を3けた区切
dataGridView1.Columns["金額"].DefaultCellStyle.Format = "#,0";

//小数第2位まで表示するだけ
dataGridView1.Columns["増加率"].DefaultCellStyle.Format = "N2";

//小数第2位まで表示して、”％”を加える。
dataGridView1.Columns["増加率"].DefaultCellStyle.Format = @"#,##0.00％";

//列指定の太字
dataGridView1.Columns["社員番号"].DefaultCellStyle.Font = new Font(dataGridView1.Font, FontStyle.Bold);


//値取得２種(値2の方が可読性が良いかな？)   
string 値1 = dataGridView1[2, 4].Value.ToString();  //【注意】[col,row] の順。 行列は0始まり。
string 値2 = dataGridView1.Rows[0].Cells["社員番号"].Value.ToString();
```

### ３．.DataSource から DataTble へ値を渡す。
```C#
            //参照渡し
            DataTable dt1 = new DataTable();
            dt1 = (DataTable)dataGridView1.DataSource;

            //値渡し
            DataTable dt2 = new DataTable();
            dt2 = ((DataTable)dataGridView1.DataSource).Copy() ;
```


### ４．セルの値によって行の色を変更する。
```C#
        private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
        {
            //セルの値によって行のバックカラーを変更する。
            DataGridView dgv = (DataGridView)sender;

            //セルの列を確認
            if (dgv.Columns[e.ColumnIndex].Name == "許可" && e.Value.ToString() == "不可")
            {   
                //単一セルのみ
                e.CellStyle.BackColor = Color.Violet;
                //対象行
                dgv.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.LightGray;
            }
            if (dgv.Columns[e.ColumnIndex].Name == "許可" && e.Value.ToString() == "")
            {   
                //そうでない場合は戻す
                //単一セルのみ
                e.CellStyle.BackColor = Color.White;
                //対象行
                dgv.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.White;
            }
        }
```



### ５．チェックボックス型セルの値確定タイミング
dataGridViewのチェックボックス型セルをクリックして、trueになった瞬間(チェックマークが表示された瞬間)に他の動作を起こしたかったが、どうしても実装できなかった。

ネットで教えてもらったのは…
「チェックマークが表示されていも内部の値は変らない、次のセルを選択した際にそれが確定される。チェックマークが表示/非表示された瞬間に値(true/false)を確定させるには、DataGridView.CommitEdit を呼ぶ必要がある」ということだった。
あのチェックマークは見えてるだけなんや～
そんなん判らんでぇ…(TT)…サンプルは以下の通り。

```C#
private void dataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
    // 0番目がチェックボックス型の列として、これだけを処理対象とする
    if (dataGridView1.CurrentCell.ColumnIndex != 0) return;

    // 値を確定する!!!
    dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
}

private void dataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
    //ここに、すぐさま起こしたいコードを書く
}
```


### ６．選択したセルの行のボトムに赤罫線を描く
![選択セルの行に赤線.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157849/592fa3af-4528-a7b5-3380-dbd2fde43216.png)

```C#
        int widthAllCol = 0;　//各カラム幅の合計値
        //private int prevSelectRow = -1;

        private void Form1_Load(object sender, EventArgs e)
        {
            //　DataTableの個所は「猫の気ままなC#日記」さんのコードを利用させてもらってます。
            //　ありがとうございます。

            DataTable dt = new DataTable("Table1");
            string[] id = { "S0001", "S0002", "S0003", "S0004", "S0005", "S0006" };
            string[] name = { "あんぱん", "メロンパン", "カレーパン", "いちごじゃむパン", "チョココロネ", "クロワッサン" };
            string[] value = { "100", "105", "110", "115", "120", "125" };

            // DataTableに列を追加します。
            dt.Columns.Add("商品番号");
            dt.Columns.Add("商品名");
            dt.Columns.Add("価格");

            // DataTableにデータを格納します。
            for (int i = 0; i < 5; i++)
            {
                dt.Rows.Add(dt.NewRow());
                dt.Rows[i]["商品番号"] = id[i];
                dt.Rows[i]["商品名"] = name[i];
                dt.Rows[i]["価格"] = value[i];
            }

            //dataGridView1に表示
            dataGridView1.DataSource = dt;

            //dataGridView1の基本設定
            dataGridView1.ReadOnly = true;                      //読取専用
            dataGridView1.AllowUserToDeleteRows = false;        //行削除禁止
            dataGridView1.AllowUserToAddRows = false;           //行挿入禁止
            dataGridView1.AllowUserToResizeRows = false;        //行の高さ変更禁止
            dataGridView1.RowHeadersVisible = false;            //行ヘッダーを非表示にする
            dataGridView1.MultiSelect = true;                  //セル、行、列が複数選択禁止
            //dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;       //セルを選択すると行全体が選択されるようにする

            //ここで全カラム幅を計算する！
            widthAllCol = 0;
            for (int i = 0; i < dataGridView1.ColumnCount; i++)
            {
                widthAllCol += dataGridView1.Columns[i].Width;    //各カラム幅の合計値
            }
        }


        private void dataGridView1_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e)
        {
            DataGridView dgv = (DataGridView)sender;

            Pen penRed = new Pen(Color.Red, 2);
            Pen penOriginal = new Pen(SystemColors.ControlDark, 1);     //デフォルトの罫線の色

            if (e.RowIndex == dgv.CurrentRow.Index)
            {
                //オリジナルコード
                //参考にさせて頂いた先
                //http://ssk-development.blogspot.com/2017/09/datagridview_5.html
                //https://tt195361.hatenablog.com/entry/2015/05/22/164840
                //int startX = dgv.RowHeadersVisible ? dgv.RowHeadersWidth : 0;
                //int startY = e.RowBounds.Top + 1;
                //int endX = startX + dgv.Columns.GetColumnsWidth(DataGridViewElementStates.Visible) - dgv.HorizontalScrollingOffset;

                int startX = 0; 
                int startY = e.RowBounds.Top + e.RowBounds.Height - 1; 
                int endX = widthAllCol; 

                e.Graphics.DrawLine(penRed,
                                    startX,
                                    startY,
                                    endX,
                                    startY);
            }
            else
            {
                int startX = 0;
                int startY = e.RowBounds.Top + e.RowBounds.Height - 1;
                int endX = widthAllCol;

                e.Graphics.DrawLine(penOriginal,
                                    startX,
                                    startY,
                                    endX,
                                    startY);
            }
        }


        private void dataGridView1_SelectionChanged(object sender, EventArgs e)
        {
            DataGridView dgv = (DataGridView)sender;

            widthAllCol = 0;
            for (int i = 0; i < dgv.ColumnCount; i++)
            {
                widthAllCol += dgv.Columns[i].Width;    //各カラム幅の合計値
            }
            
            //全面か1行づつは、処理速度見ながら判断のこと
            //書き直し(全面)
            dataGridView1.Invalidate();
            
            //書き直し(1行づつ)
            //if (prevSelectRow != -1) dataGridView1.InvalidateRow(prevSelectRow);
            //if (dataGridView1.SelectedCells.Count != 0)
            //{
            //    prevSelectRow = dataGridView1.SelectedCells[0].RowIndex;
            //    dataGridView1.InvalidateRow(prevSelectRow);
            //}
        }
```
### ７．エクセルファイルをプロジェクトに参加させ、グリッドに表示する
![エクセル参加.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157849/444b02b8-a80e-9366-b2b6-ea4cfed0fac3.png)

①エクセルファイルをプロジェクトに参加させる方法
判ってしまえば、そうかとなるのですが・・・
こちらの方々のページを参考にさせて頂きました。↓
　　http://kanesuzu-san.com/visualstudio-file-tsuika
　　https://raishin.xyz/post-201/


②エクセルファイルをdataGridView1.DataSourceに設定する方法
　　　対象ファイル：エクセル2013の.xlsxファイル
　【注意】エクセルのバージョンによって、設定値が異なります。
　参考にさせて頂いたページ↓
　　https://qiita.com/unarist/items/6cc35bb9fe502ced332f

```C#
            System.Data.OleDb.OleDbConnection MyConnection;
            System.Data.DataSet DtSet;
            System.Data.OleDb.OleDbDataAdapter MyCommand;
            MyConnection = new System.Data.OleDb.OleDbConnection(@"provider=Microsoft.ACE.OLEDB.15.0;Data Source='..\..\元データ.xlsx';Extended Properties=Excel 8.0;");
            MyCommand = new System.Data.OleDb.OleDbDataAdapter("select * from [Sheet1$]", MyConnection);
            MyCommand.TableMappings.Add("Table", "TestTable");
            DtSet = new System.Data.DataSet();
            MyCommand.Fill(DtSet);
            dataGridView1.DataSource = DtSet.Tables[0];
            MyConnection.Close();

```

### ８．データベース問合せ結果を一発でグリッドに表示する。
```C#
            using (SqlConnection connection = new SqlConnection(DB接続文字列)
            using (var command = connection.CreateCommand())
            {
                try
                {
                    // データベースの接続開始
                    connection.Open();

                    string SQL ="select ...";

                    // SQLの設定
                    command.CommandText = SQL;

                    // SQLの実行
                    var adapter = new SqlDataAdapter(command);
                    adapter.Fill(dt);
                }
                catch (Exception exception)
                {
                    Console.WriteLine(exception.Message);
                    throw;
                }
                finally
                {
                    // データベースの接続終了
                    connection.Close();
                }
            }

            dataGridView1.DataSource = dt;
```
### ９．グリッドにドラッグしたファイルを読み込む

[準備] フォーム上にDataGridViewとTextBoxを配置する。



```C#
        private void dataGridView1_DragDrop(object sender, DragEventArgs e)
        {
            //【リセット！】↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
            //表示系のリセットが必要ならここで実施
            

            //↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑

            string[] files = (string[])e.Data.GetData(DataFormats.FileDrop, false);
            for (int i = 0; i < files.Length; i++)
            {
                string fileName = files[i];
                textBoxドロップパス.Text = fileName;
            }
        }


        private void dataGridView1_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effect = DragDropEffects.All;
            }
            else
            {
                e.Effect = DragDropEffects.None;
            }
        }
```

[Sheet1]シートのデータを読み込む
```C#
    private void textBoxドロップパス_TextChanged(object sender, EventArgs e)
    {
        int errorRow = 0;
        string error = "無"; //有無

        //[準備１]Textが空白なら中止-------------------------------------------------------------------------------
        if (textBoxドロップパス.Text == "")
        {
            return;
        }

        //1)エクセル読み込み-------------------------------------------------------------------------------------
        //ht[参考]tps://www.ipentec.com/document/csharp-open-read-excel-book-and-sheet

        //改行記号を削除して代入
        string ExcelBookFileName = textBoxドロップパス.Text.Replace(Environment.NewLine, "");
        ExcelBookFileName = ExcelBookFileName.Replace("\\", "\\\\");

        //Console.WriteLine("加工後：" + ExcelBookFileName);

        //DataTbleの準備
        System.Data.DataTable dt = new System.Data.DataTable();
        DataRow dr;

        //年月日のDistinctを求める為のDictionary
        var dic年月日 = new Dictionary<string, string>();  //Key:年月日,　Value:年月日　

        //DataTableカラム追加/定義
        dt.Columns.Add("社員番号", typeof(string));
        dt.Columns.Add("氏名", typeof(string));
        dt.Columns.Add("性別", typeof(string));

       //これからエクセル読み込み！

        var excelApplication = new Microsoft.Office.Interop.Excel.Application();
        try
        {
            Workbooks workbooks = excelApplication.Workbooks;
            try
            {
                Workbook workbook = workbooks.Open(ExcelBookFileName);
                try
                {
                    Sheets worksheets = workbook.Sheets;
                    try
                    {

                        //Worksheet worksheet = worksheets[1];   
                        //の様に番号でしかシートを指定できないので↓↓↓

                        //1) シート名からシートインデックスを探す(一発の関数は無いみたい(TT))
                        int sheetId = -1;
                        int m = 0;
                        foreach (Worksheet sh in worksheets)
                        {
                            Console.WriteLine("-----m:" + m.ToString() + " / " + sh.Name);
                            m += 1;
                            if (sh.Name == "Sheet1")
                            {
                                sheetId = m;
                            }
                        }
                        if (sheetId == -1)
                        {
                            MessageBox.Show("[Sheet1]シートが見つかりません。\n前後に半角スペース等が入っていませんか？\n確認ください。");
                        }

                        //2) 対象シートを読込む
                        Worksheet worksheet = worksheets[sheetId];   //読込対象のシートを指定！！！　
                        try
                        {
                            // 使用範囲を一括で二次元配列にコピー
                            Console.WriteLine("-----これから[使用範囲を一括で二次元配列にコピー]");
                            Object[,] rangeArray;
                            Range usedRange = worksheet.UsedRange;
                            try
                            {
                                rangeArray = usedRange.Value;
                            }
                            finally
                            {
                                Marshal.ReleaseComObject(usedRange);
                            }

                            //Console.WriteLine("-----これから[(テスト)配列の並び確認]");
                            //(テスト)配列の並び確認
                            //Console.WriteLine("rangeArray[5,2].ToString(): " + rangeArray[5, 2].ToString());


                            //(テスト) 配列要素のNull判別の方法
                            // rangeArray[2, 1].ToString()); の元ファイルは空白、配列化するとNullが入入る。
                            //if (rangeArray[2, 1] == null)
                            //{
                            //    Console.WriteLine("Nullです。");
                            //}
                            //else
                            //{
                            //    Console.WriteLine("Nullではありません。");
                            //}


                            //2次元配列の長さ
                            int lastRow = rangeArray.GetLength(0);   //1次元
                            int lastCol = rangeArray.GetLength(1);   //2次元

                            Console.WriteLine("lastRow:" + lastRow);
                            Console.WriteLine("lastCol:" + lastCol);

                            //更新日時取得
                            DateTime 更新日時 = DateTime.Now;

                            // 二次元配列をループを回しながらdtに格納する。
                            //ヘッダー無し　int i = 0;
                            //ヘッダーあり　int i = 1;
                            for (int i = 0; i < lastRow; i++)
                            {
                                //Console.WriteLine("i:" + i.ToString());
                                errorRow = i + 1;

                                dr = dt.NewRow();

                                if (rangeArray[1 + i, 1] == null)
                                {
                                    dr["社員番号"] = "";
                                }
                                else
                                {
                                    dr["社員番号"] = rangeArray[1 + i, 1].ToString();
                                }

                                if (rangeArray[1 + i, 2] == null)
                                {
                                    dr["氏名"] = "";
                                }
                                else
                                {
                                    dr["氏名"]     = rangeArray[1 + i, 2].ToString();
                                }

                                if (rangeArray[1 + i, 3] == null)
                                {
                                    dr["性別"] = "";
                                }
                                else
                                {
                                    dr["性別"]     = rangeArray[1 + i, 3].ToString();
                                }

                                dt.Rows.Add(dr);
                            }
                        }
                        finally
                        {
                            Marshal.ReleaseComObject(worksheet);
                            Console.WriteLine("---------------【完了】Marshal.ReleaseComObject(worksheet);");
                        }
                    }
                    catch
                    {
                        error = "有";
                        MessageBox.Show("ファイルの読み込みに失敗しました。" +
                                                errorRow.ToString() + "行目のデータに問題があります。" +
                                                "[catch/worksheet]");
                    }
                    finally
                    {
                        Marshal.ReleaseComObject(worksheets);
                        Console.WriteLine("---------------【完了】Marshal.ReleaseComObject(worksheets);");
                    }
                }
                finally
                {
                    if (workbook != null)
                    {
                        workbook.Close(false);
                    }
                    Marshal.ReleaseComObject(workbook);
                    Console.WriteLine("---------------【完了】Marshal.ReleaseComObject(workbook);");
                }
            }
            finally { Marshal.ReleaseComObject(workbooks); }
        }
        catch
        {
            error = "有";
        }
        finally
        {
            if (excelApplication != null)
            {
                excelApplication.Quit();
            }
            Marshal.ReleaseComObject(excelApplication);
            Console.WriteLine("---------------【完了】Marshal.ReleaseComObject(excelApplication);");
        }

        if (error == "有")
        {
            Console.WriteLine("ファイルの読込に失敗しました。");
            return;
        }


        //２．dataGridView1への登録と設定-----------------------------------------------------------------------------
        //dataGridView1にセットする。
        dataGridView1.DataSource = dt;

        //dataGridView1の設定
        //ユーザの操作規制　←これはFormのLoadへ持って行っても良い
        dataGridView1.ReadOnly = true;                      //読取専用
        dataGridView1.AllowUserToDeleteRows = false;        //行削除禁止
        dataGridView1.AllowUserToAddRows = false;           //行挿入禁止
        dataGridView1.AllowUserToResizeRows = false;        //行の高さ変更禁止              
        dataGridView1.RowHeadersVisible = false;            //行ヘッダーを非表示にする
        dataGridView1.MultiSelect = false;                  //セル、行、列が複数選択禁止
        dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;       //セルを選択すると行全体が選択されるようにする

        //並び替えができないようにする
        foreach (DataGridViewColumn c in dataGridView1.Columns)
            c.SortMode = DataGridViewColumnSortMode.NotSortable;

        //基本フォントの設定
        dataGridView1.DefaultCellStyle.Font = new System.Drawing.Font("MS UI Gothic", 10);

        //ヘッダーの色等
        dataGridView1.EnableHeadersVisualStyles = false;              // Visualスタイルを使用しない
        dataGridView1.ColumnHeadersDefaultCellStyle.BackColor = Color.LightGray;
        dataGridView1.ColumnHeadersDefaultCellStyle.Font = new System.Drawing.Font("MS UI Gothic", 9);
        dataGridView1.ColumnHeadersHeight = 20;
        dataGridView1.ColumnHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;

        //カラム幅設定
        dataGridView1.Columns["社員番号"].Width = 80;
        dataGridView1.Columns["氏名"].Width = 250;
        dataGridView1.Columns["性別"].Width = 250;

        //読み込まないRowをグレーにする(ここではシート上の3行目からデータ部が始まるとする)
        for (int i = 0; i < 3 - 1; i++)
        {
            dataGridView1.Rows[i].DefaultCellStyle.BackColor = Color.Gainsboro;
        }
    }

```