C# DataGridViewの見た目をExcelのようにする

# はじめに

DataGridViewの[`CellPainting`](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datagridview.cellpainting)の勉強のためにDataGridViewの見た目をExcelっぽくしてみました。あくまで勉強用であり、実用向きではないです。動作も重いです。  

![datagridview_excel.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/edc4c595-c160-a5fe-3b38-eef9c30097ea.gif)

# 初期設定

まずはExcelっぽくする前に初期設定をします。  
* [列ヘッダや行ヘッダの色を変えるため](https://atmarkit.itmedia.co.jp/ait/articles/0610/27/news141.html)に[`EnableHeadersVisualStyles`](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datagridview.enableheadersvisualstyles)を`false`にしています。  
* [`VirtualMode`](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datagridview.virtualmode)を利用してセルへの入力をしています。  
* [ダブルバッファリング](https://e-words.jp/w/%E3%83%80%E3%83%96%E3%83%AB%E3%83%90%E3%83%83%E3%83%95%E3%82%A1%E3%83%AA%E3%83%B3%E3%82%B0.html)を有効にして、ちらつきを防止します。

```cs
//フォームいっぱいに表示
dataGridView1.Dock = DockStyle.Fill;

//行数と列数
dataGridView1.RowCount = 1000;
dataGridView1.ColumnCount = 100;

//列幅設定
foreach (DataGridViewColumn column in dataGridView1.Columns) { column.Width = 70; };

//列ヘッダースタイル
dataGridView1.EnableHeadersVisualStyles = false;
dataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;//列ヘッダーの高さ固定
dataGridView1.ColumnHeadersHeight = 26;

//セル値を入力（VirtualMode有効）
dataGridView1.VirtualMode = true;
dataGridView1.CellValueNeeded += (sender, e) => { e.Value = "R" + (e.RowIndex + 1).ToString() + "C" + (e.ColumnIndex + 1).ToString(); };

//ちらつき防止（ダブルバッファリング有効）
typeof(DataGridView).GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).SetValue(dataGridView1, true, null);
```

`CellPainting`を使用せずに通常のコードでできる範囲でExcelっぽくします。  

```cs
//枠線の色を薄グレーに変更
dataGridView1.GridColor = Color.FromArgb(212, 212, 212);

//選択されていないセルの背景色を白、文字色を黒とする。
dataGridView1.DefaultCellStyle.BackColor = Color.White;
dataGridView1.DefaultCellStyle.ForeColor = Color.Black;

//選択されているセルの背景色を薄グレー、文字色を黒とする。
dataGridView1.DefaultCellStyle.SelectionBackColor = Color.FromArgb(198, 198, 198);
dataGridView1.DefaultCellStyle.SelectionForeColor = Color.Black;

//フォントにシステムフォントを設定
dataGridView1.DefaultCellStyle.Font = SystemInformation.MenuFont;
```

# CellPaintingで見た目を変更する

## CellPaintingについて

[`CellPainting`](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datagridview.cellpainting)はセルが描画されなければならないときに発生するイベントです。第2引数の[`DataGridViewCellPaintingEventArgs`](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datagridviewcellpaintingeventargs)のプロパティ値により処理を変更することで、条件により描画内容を変更することができます。  

|プロパティ|型|内容|
|:----|:----|:----|
|[CellBounds](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datagridviewcellpaintingeventargs.cellbounds) |Rectangle|セルの境界|
|[ClipBounds](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datagridviewcellpaintingeventargs.clipbounds) |Rectangle|再描画が必要な領域|
|ColumnIndex |Int32|セルの列インデックス|
|[Graphics](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datagridviewcellpaintingeventargs.graphics) |[Graphics](https://learn.microsoft.com/ja-jp/dotnet/api/system.drawing.graphics)|セルの描画に使用されるGraphics|
|[Handled](https://learn.microsoft.com/ja-jp/dotnet/api/system.componentmodel.handledeventargs.handled) |Boolean|イベント ハンドラーがイベントを完全に処理したかどうか|
|[PaintParts](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datagridviewcellpaintingeventargs.paintparts) |[DataGridViewPaintParts](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datagridviewpaintparts)|描画されるセル部分|
|RowIndex |Int32|セルの行インデックス|
|[State](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datagridviewcellpaintingeventargs.state) |[DataGridViewElementStates](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datagridviewelementstates)|セルの状態|

例えば、セルとヘッダーで処理を変更する場合は以下のようになります。

```cs
//ヘッダーの場合はインデックスが-1になる
private void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
    if (e.ColumnIndex >= 0 && e.RowIndex >= 0)//セル
    {
        //セル用の処理
    }
    else if (e.RowIndex < 0 && e.ColumnIndex >= 0)//列ヘッダー
    {
        //列ヘッダー用の処理
    }
    else if (e.ColumnIndex < 0 && e.RowIndex >= 0)//行ヘッダー
    {
        //行ヘッダー用の処理
    }
}
```

## 事前準備

`CellPainting`のコードを実装する前に、`CellPainting`内で利用する関数などを作成します。

### 列番号をアルファベットに変換する

Excelの場合、列ヘッダーにはアルファベットが表示されるので、列番号からアルファベットを求める関数を作成します。  
```cs
private string ColumnIndex2HeaderString(int index)
{
    int amari;
    string s = "";

    if (index < 0) return "";

    for (int i = 0; i <= 100; i++)
    {
        amari = index % 26;
        s = Convert.ToChar(amari + 65).ToString() + s;
        index = (index - amari) / 26;

        if (index == 0)
        {
            break;
        }
        else
        {
            index -= 1;
        }
    }

    return s;
}
```

### 選択範囲の行番号、列番号を取得する

Excelの場合、選択されている行や列のヘッダーが強調表示されるので、現在選択されている行番号と列番号を配列として確保する関数を作成します。  

```cs
private (HashSet<int> RowIndexs, HashSet<int> ColumnIndexs) getSelectionIndexs()
{
    HashSet<int> row = new HashSet<int>();
    HashSet<int> column = new HashSet<int>();

    foreach (DataGridViewCell cell in dataGridView1.SelectedCells)
    {
        row.Add(cell.RowIndex);
        column.Add(cell.ColumnIndex);
    }

    return (row, column);
}
```

### 選択範囲の最初のセルを取得する

Excelの場合、選択範囲の最初のセルだけは、薄グレーでなく白のままとなります。選択範囲が変更された場合、[`SelectionChanged`](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datagridview.selectionchanged)イベントを利用して選択範囲の最初のセルの行列番号を確保します。

```cs
//選択範囲の最初のセル
int SelectionStartRowIndex = 0;
int SelectionStartColumnIndex = 0;

//セル範囲変更時
private void dataGridView1_SelectionChanged(object sender, EventArgs e)
{
    //選択範囲の最初のセルの行番号、列番号をストック
    bool isFirstCell = true;
    var cell = dataGridView1.CurrentCell;

    //上のセルが選択されていない
    if (isFirstCell)
    {
        isFirstCell = cell.RowIndex == 0 || (0 <= cell.RowIndex - 1 && !dataGridView1[cell.ColumnIndex, cell.RowIndex - 1].Selected);
    }
    //下のセルが選択されていない
    if (isFirstCell)
    {
        isFirstCell = cell.RowIndex == dataGridView1.RowCount - 1 || (cell.RowIndex + 1 < dataGridView1.RowCount && !dataGridView1[cell.ColumnIndex, cell.RowIndex + 1].Selected);
    }
    //左のセルが選択されていない
    if (isFirstCell)
    {
        isFirstCell = cell.ColumnIndex == 0 || (0 <= cell.ColumnIndex - 1 && !dataGridView1[cell.ColumnIndex - 1, cell.RowIndex].Selected);
    }
    //右のセルが選択されていない
    if (isFirstCell)
    {
        isFirstCell = cell.ColumnIndex == dataGridView1.ColumnCount - 1 || (cell.ColumnIndex + 1 < dataGridView1.ColumnCount && !dataGridView1[cell.ColumnIndex + 1, cell.RowIndex].Selected);
    }

    //左右上下いずれも選択状態にない場合は選択範囲の最初のセルとみなす
    if (isFirstCell)
    {
        SelectionStartRowIndex = dataGridView1.CurrentCell.RowIndex;
        SelectionStartColumnIndex = dataGridView1.CurrentCell.ColumnIndex;
    }
}
```
## CellPaintingを実装する

### 大枠を実装する

処理としては、

* セルの場合：選択範囲の最初のセルかそうでないかで処理を分ける。また選択範囲かどうかで処理を分ける。
* 列ヘッダーの場合：選択されているかどうかで処理を分ける。列番号（アルファベット）を描画する。
* 行ヘッダーの場合：選択されているかどうかで処理を分ける。行番号を描画する。

となります。

```cs
private void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
    //選択範囲を取得
    var SelectionIndexs = getSelectionIndexs();

    if (e.ColumnIndex >= 0 && e.RowIndex >= 0)//セルの処理
    {
        //選択範囲の最初のセルかどうか
        if ((e.State & DataGridViewElementStates.Selected) == DataGridViewElementStates.Selected
            && SelectionStartRowIndex == e.RowIndex && SelectionStartColumnIndex == e.ColumnIndex)
        {
            //選択範囲の最初のセルの場合の処理
        }
        else
        {
            //選択範囲の最初のセル以外の処理
        }

        if ((e.State & DataGridViewElementStates.Selected) == DataGridViewElementStates.Selected)//選択範囲の場合
        {
            //選択範囲の場合の処理
        }
        else//選択範囲以外
        {
            //選択範囲以外の場合の処理
        }

        //処理完了
        e.Handled = true;
    }
    else if (e.RowIndex < 0 && e.ColumnIndex >= 0)//列ヘッダーの処理
    {
        //選択されているかどうか
        if (SelectionIndexs.ColumnIndexs.Contains(e.ColumnIndex))
        {
            //選択されている列の場合の処理
        }

        //その他の処理

        //列番号描画処理

        //処理完了
        e.Handled = true;
    }
    else if (e.ColumnIndex < 0 && e.RowIndex >= 0)//行ヘッダーの処理
    {
        //選択されているかどうか
        if (SelectionIndexs.RowIndexs.Contains(e.RowIndex))
        {
            //選択されている列の場合の処理
        }

        //その他の処理

        //行番号描画処理

        //処理完了
        e.Handled = true;
    }
}
```

### 実際に実装する

具体的な実装をするにあたり、以下を参考にさせていただきました。  

* [DataGridViewのセルを自分で描画する - .NET Tips (VB.NET,C#...)](https://dobon.net/vb/dotnet/datagridview/ownerdrawcell.html)  
* [DataGridView コントロールのセルの外観をカスタマイズする - Windows Forms .NET Framework | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/desktop/winforms/controls/customize-the-appearance-of-cells-in-the-datagrid?view=netframeworkdesktop-4.8)  
* [DataGridViewCellPaintingEventArgs.Paint メソッド (System.Windows.Forms) | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datagridviewcellpaintingeventargs.paint)  
* [Graphics.DrawLine メソッド (System.Drawing) | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/api/system.drawing.graphics.drawline)  
* [Graphics.DrawRectangle メソッド (System.Drawing) | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/api/system.drawing.graphics.drawrectangle)  
* [DataGridViewの行ヘッダーに行番号を表示する - .NET Tips (VB.NET,C#...)](https://dobon.net/vb/dotnet/datagridview/drawrownumber.html)  

```cs
private void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
    //枠描画用
    Pen pen_border = new Pen(new SolidBrush(Color.FromArgb(33, 115, 70)));

    //選択範囲
    var SelectionIndexs = getSelectionIndexs();

    if (e.ColumnIndex >= 0 && e.RowIndex >= 0)//セル
    {
        //選択範囲の最初のセルの場合は背景を除き、通常通り描画
        if ((e.State & DataGridViewElementStates.Selected) == DataGridViewElementStates.Selected
            && SelectionStartRowIndex == e.RowIndex && SelectionStartColumnIndex == e.ColumnIndex)
        {
            DataGridViewPaintParts paintParts = e.PaintParts & ~DataGridViewPaintParts.SelectionBackground;
            e.Paint(e.ClipBounds, paintParts);
        }
        else
        {
            e.Paint(e.ClipBounds, e.PaintParts);
        }

        //追加描画
        if ((e.State & DataGridViewElementStates.Selected) == DataGridViewElementStates.Selected)//選択範囲の場合
        {
            //選択範囲の右の線
            if ((e.ColumnIndex + 1 < dataGridView1.ColumnCount && !dataGridView1[e.ColumnIndex + 1, e.RowIndex].Selected)
                || e.ColumnIndex == dataGridView1.ColumnCount - 1)
            {
                int x = e.CellBounds.X + e.CellBounds.Width - 1;
                e.Graphics.DrawLine(pen_border, x, e.CellBounds.Y, x, e.CellBounds.Y + e.CellBounds.Height - 1);
            }

            //選択範囲の下の線
            if ((e.RowIndex + 1 < dataGridView1.RowCount && !dataGridView1[e.ColumnIndex, e.RowIndex + 1].Selected)
                || e.RowIndex == dataGridView1.RowCount - 1)
            {
                int y = e.CellBounds.Y + e.CellBounds.Height - 1;
                e.Graphics.DrawLine(pen_border, e.CellBounds.X, y, e.CellBounds.X + e.CellBounds.Width - 1, y);
            }
        }
        else//選択範囲以外
        {
            if (e.ColumnIndex + 1 < dataGridView1.ColumnCount && dataGridView1[e.ColumnIndex + 1, e.RowIndex].Selected)//選択範囲の左に隣接しているセル
            {
                e.Graphics.DrawRectangle(pen_border, e.CellBounds.X + e.CellBounds.Width - 2, e.CellBounds.Y, 1, e.CellBounds.Height - 1);
            }
            if (0 <= e.ColumnIndex - 1 && dataGridView1[e.ColumnIndex - 1, e.RowIndex].Selected)//選択範囲の右に隣接しているセル
            {
                e.Graphics.DrawRectangle(pen_border, e.CellBounds.X - 1, e.CellBounds.Y, 1, e.CellBounds.Height - 1);
            }
            if (e.RowIndex + 1 < dataGridView1.RowCount && dataGridView1[e.ColumnIndex, e.RowIndex + 1].Selected)//選択範囲の上に隣接しているセル
            {
                e.Graphics.DrawRectangle(pen_border, e.CellBounds.X, e.CellBounds.Y + e.CellBounds.Height - 2, e.CellBounds.Width - 1, 1);
            }
            if (0 <= e.RowIndex - 1 && dataGridView1[e.ColumnIndex, e.RowIndex - 1].Selected)//選択範囲の下に隣接しているセル
            {
                e.Graphics.DrawRectangle(pen_border, e.CellBounds.X, e.CellBounds.Y - 1, e.CellBounds.Width - 1, 1);
            }
        }
        e.Handled = true;
    }
    else if (e.RowIndex < 0 && e.ColumnIndex >= 0)//列ヘッダー
    {
        //通常通り描画
        e.Paint(e.ClipBounds, e.PaintParts);

        //選択されている列であれば
        bool selected = false;
        if (SelectionIndexs.ColumnIndexs.Contains(e.ColumnIndex))
        {
            selected = true;

            //強調ラインを引く
            Rectangle rect = dataGridView1.GetCellDisplayRectangle(e.ColumnIndex, dataGridView1.FirstDisplayedScrollingRowIndex, true);
            e.Graphics.DrawRectangle(pen_border, rect.X, dataGridView1.ColumnHeadersHeight - 1, rect.Width - 1, 1);
        }

        //選択有無で背景色を変更
        dataGridView1.Columns[e.ColumnIndex].HeaderCell.Style.BackColor = selected ? Color.FromArgb(210, 210, 210) : Color.FromArgb(230, 230, 230);

        //列番号描画
        Rectangle indexRect = e.CellBounds;
        indexRect.Inflate(-2, -2);
        TextRenderer.DrawText(
            e.Graphics,
            ColumnIndex2HeaderString(e.ColumnIndex),
            new Font(SystemInformation.MenuFont.FontFamily, 10F, FontStyle.Regular),
            indexRect,
            selected ? Color.FromArgb(27, 93, 57) : Color.Black,
            TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);

        e.Handled = true;
    }
    else if (e.ColumnIndex < 0 && e.RowIndex >= 0)//行ヘッダー
    {
        //通常通り描画
        e.Paint(e.ClipBounds, e.PaintParts);

        //選択されている行であれば
        bool selected = false;
        if (SelectionIndexs.RowIndexs.Contains(e.RowIndex))
        {
            selected = true;

            //強調ラインを引く
            Rectangle rect = dataGridView1.GetCellDisplayRectangle(dataGridView1.FirstDisplayedScrollingColumnIndex, e.RowIndex, true);
            e.Graphics.DrawRectangle(pen_border, dataGridView1.RowHeadersWidth - 1, rect.Y, 1, rect.Height - 1);
        }

        //選択有無で背景色を変更
        dataGridView1.Rows[e.RowIndex].HeaderCell.Style.BackColor = selected ? Color.FromArgb(210, 210, 210) : Color.FromArgb(230, 230, 230);

        //行番号描画
        Rectangle indexRect = e.CellBounds;
        indexRect.Inflate(-2, -2);
        TextRenderer.DrawText(
            e.Graphics,
            (e.RowIndex + 1).ToString(),
            new Font(SystemInformation.MenuFont.FontFamily, 10F, FontStyle.Regular),
            indexRect,
            selected ? Color.FromArgb(27, 93, 57) : Color.Black,
            TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);

        e.Handled = true;
    }
}
```

# 実装コード

実装コードの全てです。Form上にDataGridViewを1個配置しています(dataGridView1)。  
```cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            //フォームいっぱいに表示
            dataGridView1.Dock = DockStyle.Fill;

            //行数と列数
            dataGridView1.RowCount = 1000;
            dataGridView1.ColumnCount = 100;

            //列幅設定
            foreach (DataGridViewColumn column in dataGridView1.Columns) { column.Width = 70; };

            //列ヘッダースタイル
            dataGridView1.EnableHeadersVisualStyles = false;
            dataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;//列ヘッダーの高さ固定
            dataGridView1.ColumnHeadersHeight = 26;

            //セル値を入力（VirtualMode有効）
            dataGridView1.VirtualMode = true;
            dataGridView1.CellValueNeeded += (sender, e) => { e.Value = "R" + (e.RowIndex + 1).ToString() + "C" + (e.ColumnIndex + 1).ToString(); };

            //ちらつき防止（ダブルバッファリング有効）
            typeof(DataGridView).GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).SetValue(dataGridView1, true, null);

            //枠線の色を薄グレーに変更
            dataGridView1.GridColor = Color.FromArgb(212, 212, 212);

            //選択されていないセルの背景色を白、文字色を黒とする。
            dataGridView1.DefaultCellStyle.BackColor = Color.White;
            dataGridView1.DefaultCellStyle.ForeColor = Color.Black;

            //選択されているセルの背景色を薄グレー、文字色を黒とする。
            dataGridView1.DefaultCellStyle.SelectionBackColor = Color.FromArgb(198, 198, 198);
            dataGridView1.DefaultCellStyle.SelectionForeColor = Color.Black;

            //フォントにシステムフォントを設定
            dataGridView1.DefaultCellStyle.Font = SystemInformation.MenuFont;

            //描画を通常のものから変更
            dataGridView1.CellPainting += (sender, e) => { dataGridView1_CellPainting(sender, e); };

            //選択範囲変更時
            dataGridView1.SelectionChanged += (sender, e) => { dataGridView1_SelectionChanged(sender, e); };
        }

        private void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
        {
            //枠描画用
            Pen pen_border = new Pen(new SolidBrush(Color.FromArgb(33, 115, 70)));

            //選択範囲
            var SelectionIndexs = getSelectionIndexs();

            if (e.ColumnIndex >= 0 && e.RowIndex >= 0)//セル
            {
                //選択範囲の最初のセルの場合は背景を除き、通常通り描画
                if ((e.State & DataGridViewElementStates.Selected) == DataGridViewElementStates.Selected
                    && SelectionStartRowIndex == e.RowIndex && SelectionStartColumnIndex == e.ColumnIndex)
                {
                    DataGridViewPaintParts paintParts = e.PaintParts & ~DataGridViewPaintParts.SelectionBackground;
                    e.Paint(e.ClipBounds, paintParts);
                }
                else
                {
                    e.Paint(e.ClipBounds, e.PaintParts);
                }

                //追加描画
                if ((e.State & DataGridViewElementStates.Selected) == DataGridViewElementStates.Selected)//選択範囲の場合
                {
                    //選択範囲の右の線
                    if ((e.ColumnIndex + 1 < dataGridView1.ColumnCount && !dataGridView1[e.ColumnIndex + 1, e.RowIndex].Selected)
                        || e.ColumnIndex == dataGridView1.ColumnCount - 1)
                    {
                        int x = e.CellBounds.X + e.CellBounds.Width - 1;
                        e.Graphics.DrawLine(pen_border, x, e.CellBounds.Y, x, e.CellBounds.Y + e.CellBounds.Height - 1);
                    }

                    //選択範囲の下の線
                    if ((e.RowIndex + 1 < dataGridView1.RowCount && !dataGridView1[e.ColumnIndex, e.RowIndex + 1].Selected)
                        || e.RowIndex == dataGridView1.RowCount - 1)
                    {
                        int y = e.CellBounds.Y + e.CellBounds.Height - 1;
                        e.Graphics.DrawLine(pen_border, e.CellBounds.X, y, e.CellBounds.X + e.CellBounds.Width - 1, y);
                    }
                }
                else//選択範囲以外
                {
                    if (e.ColumnIndex + 1 < dataGridView1.ColumnCount && dataGridView1[e.ColumnIndex + 1, e.RowIndex].Selected)//選択範囲の左に隣接しているセル
                    {
                        e.Graphics.DrawRectangle(pen_border, e.CellBounds.X + e.CellBounds.Width - 2, e.CellBounds.Y, 1, e.CellBounds.Height - 1);
                    }
                    if (0 <= e.ColumnIndex - 1 && dataGridView1[e.ColumnIndex - 1, e.RowIndex].Selected)//選択範囲の右に隣接しているセル
                    {
                        e.Graphics.DrawRectangle(pen_border, e.CellBounds.X - 1, e.CellBounds.Y, 1, e.CellBounds.Height - 1);
                    }
                    if (e.RowIndex + 1 < dataGridView1.RowCount && dataGridView1[e.ColumnIndex, e.RowIndex + 1].Selected)//選択範囲の上に隣接しているセル
                    {
                        e.Graphics.DrawRectangle(pen_border, e.CellBounds.X, e.CellBounds.Y + e.CellBounds.Height - 2, e.CellBounds.Width - 1, 1);
                    }
                    if (0 <= e.RowIndex - 1 && dataGridView1[e.ColumnIndex, e.RowIndex - 1].Selected)//選択範囲の下に隣接しているセル
                    {
                        e.Graphics.DrawRectangle(pen_border, e.CellBounds.X, e.CellBounds.Y - 1, e.CellBounds.Width - 1, 1);
                    }
                }
                e.Handled = true;
            }
            else if (e.RowIndex < 0 && e.ColumnIndex >= 0)//列ヘッダー
            {
                //通常通り描画
                e.Paint(e.ClipBounds, e.PaintParts);

                //選択されている列であれば
                bool selected = false;
                if (SelectionIndexs.ColumnIndexs.Contains(e.ColumnIndex))
                {
                    selected = true;

                    //強調ラインを引く
                    Rectangle rect = dataGridView1.GetCellDisplayRectangle(e.ColumnIndex, dataGridView1.FirstDisplayedScrollingRowIndex, true);
                    e.Graphics.DrawRectangle(pen_border, rect.X, dataGridView1.ColumnHeadersHeight - 1, rect.Width - 1, 1);
                }

                //選択有無で背景色を変更
                dataGridView1.Columns[e.ColumnIndex].HeaderCell.Style.BackColor = selected ? Color.FromArgb(210, 210, 210) : Color.FromArgb(230, 230, 230);

                //列番号描画
                Rectangle indexRect = e.CellBounds;
                indexRect.Inflate(-2, -2);
                TextRenderer.DrawText(
                    e.Graphics,
                    ColumnIndex2HeaderString(e.ColumnIndex),
                    new Font(SystemInformation.MenuFont.FontFamily, 10F, FontStyle.Regular),
                    indexRect,
                    selected ? Color.FromArgb(27, 93, 57) : Color.Black,
                    TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);

                e.Handled = true;
            }
            else if (e.ColumnIndex < 0 && e.RowIndex >= 0)//行ヘッダー
            {
                //通常通り描画
                e.Paint(e.ClipBounds, e.PaintParts);

                //選択されている行であれば
                bool selected = false;
                if (SelectionIndexs.RowIndexs.Contains(e.RowIndex))
                {
                    selected = true;

                    //強調ラインを引く
                    Rectangle rect = dataGridView1.GetCellDisplayRectangle(dataGridView1.FirstDisplayedScrollingColumnIndex, e.RowIndex, true);
                    e.Graphics.DrawRectangle(pen_border, dataGridView1.RowHeadersWidth - 1, rect.Y, 1, rect.Height - 1);
                }

                //選択有無で背景色を変更
                dataGridView1.Rows[e.RowIndex].HeaderCell.Style.BackColor = selected ? Color.FromArgb(210, 210, 210) : Color.FromArgb(230, 230, 230);

                //行番号描画
                Rectangle indexRect = e.CellBounds;
                indexRect.Inflate(-2, -2);
                TextRenderer.DrawText(
                    e.Graphics,
                    (e.RowIndex + 1).ToString(),
                    new Font(SystemInformation.MenuFont.FontFamily, 10F, FontStyle.Regular),
                    indexRect,
                    selected ? Color.FromArgb(27, 93, 57) : Color.Black,
                    TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);

                e.Handled = true;
            }
        }

        //列番号→列アルファベット
        private string ColumnIndex2HeaderString(int index)
        {
            int amari;
            string s = "";

            if (index < 0) return "";

            for (int i = 0; i <= 100; i++)
            {
                amari = index % 26;
                s = Convert.ToChar(amari + 65).ToString() + s;
                index = (index - amari) / 26;

                if (index == 0)
                {
                    break;
                }
                else
                {
                    index -= 1;
                }
            }

            return s;
        }

        //選択されている行、列番号の配列
        private (HashSet<int> RowIndexs, HashSet<int> ColumnIndexs) getSelectionIndexs()
        {
            HashSet<int> row = new HashSet<int>();
            HashSet<int> column = new HashSet<int>();

            foreach (DataGridViewCell cell in dataGridView1.SelectedCells)
            {
                row.Add(cell.RowIndex);
                column.Add(cell.ColumnIndex);
            }

            return (row, column);
        }

        //選択範囲の最初のセル
        int SelectionStartRowIndex = 0;
        int SelectionStartColumnIndex = 0;

        //セル範囲変更時
        private void dataGridView1_SelectionChanged(object sender, EventArgs e)
        {
            //選択範囲の最初のセルの行番号、列番号をストック
            bool isFirstCell = true;
            var cell = dataGridView1.CurrentCell;

            //上のセルが選択されていない
            if (isFirstCell)
            {
                isFirstCell = cell.RowIndex == 0 || (0 <= cell.RowIndex - 1 && !dataGridView1[cell.ColumnIndex, cell.RowIndex - 1].Selected);
            }
            //下のセルが選択されていない
            if (isFirstCell)
            {
                isFirstCell = cell.RowIndex == dataGridView1.RowCount - 1 || (cell.RowIndex + 1 < dataGridView1.RowCount && !dataGridView1[cell.ColumnIndex, cell.RowIndex + 1].Selected);
            }
            //左のセルが選択されていない
            if (isFirstCell)
            {
                isFirstCell = cell.ColumnIndex == 0 || (0 <= cell.ColumnIndex - 1 && !dataGridView1[cell.ColumnIndex - 1, cell.RowIndex].Selected);
            }
            //右のセルが選択されていない
            if (isFirstCell)
            {
                isFirstCell = cell.ColumnIndex == dataGridView1.ColumnCount - 1 || (cell.ColumnIndex + 1 < dataGridView1.ColumnCount && !dataGridView1[cell.ColumnIndex + 1, cell.RowIndex].Selected);
            }

            //左右上下いずれも選択状態にない場合は選択範囲の最初のセルとみなす
            if (isFirstCell)
            {
                SelectionStartRowIndex = dataGridView1.CurrentCell.RowIndex;
                SelectionStartColumnIndex = dataGridView1.CurrentCell.ColumnIndex;
            }

            //表示更新
            dataGridView1.Refresh();
        }
    }
}
```