【C#】DataGridViewにカスタムフッターを追加して列幅の同期と水平スクロールの実装

Windows Formsアプリケーションで DataGridView を使用する際、デフォルトではフッター行が提供されていません。しかし、合計行や特定の情報を表示するためにフッターを追加したい場合があります。この記事では、DataGridView にカスタムフッターを追加し、列幅の同期や水平スクロールを実装する方法を解説します。
<h2>実装の特徴</h2>
<ol>
<ul>
      <li><b>列幅の同期:</b>メインのDataGridView とフッターの列幅を同期</li>
      <li><b>水平スクロールの同期: </b> メインのDataGridView とフッターの水平スクロールを連動</li>
       <li><b>フッターの固定表示:</b>垂直スクロール時にフッターが常に下部に表示される</li>
    </ul>
    <hr>
    
 <li> <b>メインのDataGridViewの設定</li></b>
DataGridViewの設定は、SetupDataGridViewメソッドでは、以下のような設定を行います。

```DataGridViewWithFooter.cs
private void InitializeDataGridView()
{

    dt = new DataTable();

    // Create columns
    dt.Columns.Add("項目", typeof(string));
    dt.Columns.Add("数量", typeof(int));
    dt.Columns.Add("価格", typeof(decimal));
    dt.Columns.Add("合計", typeof(decimal));
    
    dataGridView1.DataSource = dt;
    dataGridView1.Columns["項目"].Width = 100;
    dataGridView1.Columns["数量"].Width = 100;
    dataGridView1.Columns["価格"].Width = 100;
    dataGridView1.Columns["合計"].Width = 100;

    // Configure DataGridView
    //dataGridView1.AllowUserToAddRows = false;
    dataGridView1.BorderStyle = BorderStyle.None;
    dataGridView1.RowHeadersVisible = false;
    //dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
    dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
    dataGridViewFooter.SendToBack();

    dataGridView1.Columns["項目"].ReadOnly = true;
    dataGridView1.Columns["合計"].ReadOnly = true;

    // Set up events
    // DataGridViewのScrollイベントに紐付け
    dataGridView1.Scroll += (s, e) => SyncFooterScroll();
    dataGridView1.ColumnWidthChanged += (s, e) => SyncFooterColumns(); // Sync column widths
    dataGridView1.CellValueChanged += DataGridView1_CellValueChanged; // Handle cell value changes

    this.Load += MainForm_Load;
}
```
<b> <li>カスタムフッターの実装</li></b>
フッターとして機能する別の DataGridView を追加します。このフッターは、メインの DataGridView の下部に固定され、列幅やスクロールが同期されます。フッターの DataGridView を初期化し、メインの DataGridView と同期させます。  
    
```DataGridViewWithFooter.cs
private void InitializeFooter()
{
    // フッターの設定
    dataGridViewFooter.AllowUserToAddRows = false;
    dataGridViewFooter.AllowUserToResizeRows = false;
    dataGridViewFooter.RowHeadersVisible = false;
    dataGridViewFooter.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
    dataGridViewFooter.BackgroundColor = SystemColors.Control;
    dataGridViewFooter.BorderStyle = BorderStyle.None;
    dataGridViewFooter.Height = 25; // フッターの高さ
    dataGridViewFooter.ReadOnly = true;
    dataGridViewFooter.DefaultCellStyle.Font = new Font(dataGridView1.Font, FontStyle.Bold);
    dataGridViewFooter.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;

    // フォームにフッターを追加
    this.Controls.Add(dataGridViewFooter);
    this.Controls.SetChildIndex(dataGridViewFooter, 0); // フッターを前面に表示

    // 列の同期
    SyncFooterColumns();

    // フッターの位置を更新
    UpdateFooterPosition();
}
```
<b><li>列幅の同期</li></b>
メインの DataGridView とフッターの列幅を同期するために、SyncFooterColumns メソッドを実装します.

```DataGridViewWithFooter.cs
private void SyncFooterColumns()
{
    // 既存の列をクリア
    dataGridViewFooter.Columns.Clear();

    // メインのDataGridViewの列をフッターに追加
    foreach (DataGridViewColumn column in dataGridView1.Columns)
    {
        var footerColumn = new DataGridViewTextBoxColumn
        {
            Name = column.Name,
            HeaderText = column.HeaderText,
            Width = column.Width,
            ReadOnly = true,
            SortMode = DataGridViewColumnSortMode.NotSortable
        };

        dataGridViewFooter.Columns.Add(footerColumn);
    }
}
```
<b><li> 水平スクロールの同期</li></b>
メインの DataGridView とフッターの水平スクロールを同期するために、SyncFooterScroll メソッドを実装します。

```DataGridViewWithFooter.cs
private void SyncFooterScroll()
{
    // メインのDataGridViewとフッターの水平スクロールを同期
    dataGridViewFooter.HorizontalScrollingOffset = dataGridView1.HorizontalScrollingOffset;
}
```

<b><li>フッターの位置調整</li></b>
垂直スクロール時にフッターが常に下部に表示されるように、UpdateFooterPosition メソッドを実装します。

```DataGridViewWithFooter.cs
private void UpdateFooterPosition()
{
    // フッターの位置をメインのDataGridViewの下部に設定
    dataGridViewFooter.Top = dataGridView1.Bottom - 40; // 位置を調整
    dataGridViewFooter.Left = dataGridView1.Left;
    dataGridViewFooter.Width = dataGridView1.Width - SystemInformation.VerticalScrollBarWidth; // 垂直スクロールバーの幅を考慮
}
```
<b><li>合計行の計算</li></b>
フッターに合計行を表示するために、CalculateTotals メソッドを実装します。

```DataGridViewWithFooter.cs
private void CalculateTotals()
{
    decimal quantitySum = 0;
    decimal priceSum = 0;
    decimal totalSum = 0;

    foreach (DataRow row in dt.Rows)
    {
        quantitySum += Convert.ToDecimal(row["数量"]);
        priceSum += Convert.ToDecimal(row["価格"]);
        row["合計"] = Convert.ToDecimal(row["数量"]) * Convert.ToDecimal(row["価格"]);
        totalSum += Convert.ToDecimal(row["合計"]);
    }

    // フッターのヘッダーテキストを更新
    dataGridViewFooter.Columns["項目"].HeaderText = "合計";
    dataGridViewFooter.Columns["数量"].HeaderText = quantitySum.ToString();
    dataGridViewFooter.Columns["価格"].HeaderText = priceSum.ToString("C", CultureInfo.GetCultureInfo("ja-JP"));
    dataGridViewFooter.Columns["合計"].HeaderText = totalSum.ToString("C", CultureInfo.GetCultureInfo("ja-JP"));
}
```
<b><li>サンプルデータの追加</li></b>
フォームのロード時にサンプルデータを追加します。

```DataGridViewWithFooter.cs
private void MainForm_Load(object sender, EventArgs e)
{
    // サンプルデータを追加
    dt.Rows.Add("ブラックコーヒー", 3, 125.50m);
    dt.Rows.Add("ミルクコーヒー", 2, 155.00m);
    dt.Rows.Add("カフェオレ", 5, 175.05m);

    // 合計を計算
    CalculateTotals();
};
```
</ol>

<h2> 実行結果</h2> 
<ol>
<ul>
      <li><b>メインの DataGridView とフッターの列幅が同期されます。</li>
      <li><b>水平スクロールが連動します。</b></li>
      <li><b>垂直スクロール時にフッターが常に下部に表示されます。</b></li>
</ol>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3951101/fe0ddb40-7043-4f48-aa9b-e968df9ddd4a.png)


## まとめ
DataGridView にカスタムフッターを追加し、列幅の同期や水平スクロールを実装する方法を解説しました。このテクニックを使用することで、より柔軟でユーザーフレンドリーなデータ表示が可能になります。