C# DataGridViewのセルのToolTipをカスタマイズする方法

#概要
DataGridViewのセルにカーソルを合わせたときに表示されるToolTipの見た目のカスタマイズ方法です。
カスタマイズ後のイメージが以下になります。
ToolTipにはセルのX、Y座標とセルの値を表示しています。
背景を黒で塗りつぶして文字と枠線を白にしています。
さらにフォントもデフォルトより大きくしています。

|通常|カスタマイズ|
|:-:|:-:|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411151/dcdf224b-d196-33c4-a588-2c708da13152.png)|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411151/d03562e6-2583-4b34-a1b3-ecceb8ac1514.png)|

#環境
- Visual Studio 2019
- .Net Framework 4.8

#コード
ToolTipコントロールを作成し、OwnerDraw、ShowAlwaysプロパティにtrueを設定します。
Draw、Popupにイベントを設定します。
ここまではデザイナを使用して行っても構いません。

次にDataGridViewのtoolTip変数に作成したToolTipコントロールを設定します。
DataGridViewのtoolTipはプロパティが無いので、GetFieldメソッドやSetValueメソッドを使用して設定しています。

- DataGridViewからtoolTipControlを取得
- toolTipControlからtoolTipを取得
- toolTipに作成したToolTipコントロールを設定

```C#
private void Form1_Load(object sender, EventArgs e)
{
    this.InitDataGridView();

    var cellToolTip = new ToolTip();
    cellToolTip.OwnerDraw = true;
    cellToolTip.ShowAlways = true;
    cellToolTip.Draw += new DrawToolTipEventHandler(this.CellToolTip_Draw);
    cellToolTip.Popup += new PopupEventHandler(this.CellToolTip_Popup);

    var toolTipControl = this.dataGridView1.GetType().GetField(
        "toolTipControl",
        System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic
        ).GetValue(this.dataGridView1);

    var toolTip = toolTipControl.GetType().GetField(
        "toolTip",
        System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic
        );
    toolTip.SetValue(toolTipControl, cellToolTip);
}
```

CellFormattingイベントでセルのToolTipTextプロパティに表示させたい内容を設定します。
今回はセルのX、Y座標とセルの値を設定しています。

```C#
private void TargetDataGridView_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
    if (e.ColumnIndex >= 0 && e.RowIndex >= 0)
    {
        var cell = this.dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex];
        cell.ToolTipText = $"(X={e.ColumnIndex}, Y={e.RowIndex}){Environment.NewLine}";
        cell.ToolTipText += $"Value={e.Value}";
    }
}
```
フォントを変更すると表示領域のサイズも変化する可能性があるため、PopupイベントでToolTipのサイズを設定します。

```C#
private void CellToolTip_Popup(object sender, PopupEventArgs e)
{
    e.ToolTipSize = new Size(e.ToolTipSize.Width + 30, e.ToolTipSize.Height + 9);
}
```
DrawイベントでToolTipの描画部分を記述します。
塗りつぶしや枠線、文字の色、文字のフォントなどをここで設定します。
枠線のコードを省略すると枠線なしのToolTipになります。

```C#
private void CellToolTip_Draw(object sender, DrawToolTipEventArgs e)
{
    Color backColor = Color.Black;
    Color borderColor = Color.White;
    Color foreColor = Color.White;

    //塗りつぶし
    using (SolidBrush brush = new SolidBrush(backColor))
        e.Graphics.FillRectangle(brush, e.Bounds);
    //枠線
    using (Pen pen = new Pen(borderColor))
        e.Graphics.DrawRectangle(
            pen,
            new Rectangle(
                e.Bounds.X,
                e.Bounds.Y,
                e.Bounds.Width - 1,
                e.Bounds.Height - 1
                )
            );
    //テキスト描画
    using (Font font = new Font("Meiryo UI", 10f))
        TextRenderer.DrawText(
            e.Graphics,
            e.ToolTipText,
            font,
            e.Bounds,
            foreColor,
            TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter
            );
}
```
コード全体です。
InitDataGridViewメソッドでDataGridViewの設定と行列を追加しています。
RandomStringメソッドはDataGridViewのセルの値を作成しています。

```c#
using System;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;

namespace DataGridViewSample
{
    public partial class Form1 : Form
    {
        private Random _Random = new Random();

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            try
            {
                this.InitDataGridView();

                var cellToolTip = new ToolTip();
                cellToolTip.OwnerDraw = true;
                cellToolTip.ShowAlways = true;
                cellToolTip.Draw += new DrawToolTipEventHandler(this.CellToolTip_Draw);
                cellToolTip.Popup += new PopupEventHandler(this.CellToolTip_Popup);

                var toolTipControl = this.dataGridView1.GetType().GetField(
                    "toolTipControl",
                    System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic
                    ).GetValue(this.dataGridView1);

                var toolTip = toolTipControl.GetType().GetField(
                    "toolTip",
                    System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic
                    );
                toolTip.SetValue(toolTipControl, cellToolTip);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void TargetDataGridView_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
        {
            try
            {
                if (e.ColumnIndex >= 0 && e.RowIndex >= 0)
                {
                    var cell = this.dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex];
                    cell.ToolTipText = $"(X={e.ColumnIndex}, Y={e.RowIndex}){Environment.NewLine}";
                    cell.ToolTipText += $"Value={e.Value}";
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void CellToolTip_Popup(object sender, PopupEventArgs e)
        {
            e.ToolTipSize = new Size(e.ToolTipSize.Width + 30, e.ToolTipSize.Height + 9);
        }

        private void CellToolTip_Draw(object sender, DrawToolTipEventArgs e)
        {
            try
            {
                Color backColor = Color.Black;
                Color borderColor = Color.White;
                Color foreColor = Color.White;

                //塗りつぶし
                using (SolidBrush brush = new SolidBrush(backColor))
                    e.Graphics.FillRectangle(brush, e.Bounds);
                //枠線
                using (Pen pen = new Pen(borderColor))
                    e.Graphics.DrawRectangle(
                        pen,
                        new Rectangle(
                            e.Bounds.X,
                            e.Bounds.Y,
                            e.Bounds.Width - 1,
                            e.Bounds.Height - 1
                            )
                        );
                //テキスト描画
                using (Font font = new Font("Meiryo UI", 10f))
                    TextRenderer.DrawText(
                        e.Graphics,
                        e.ToolTipText,
                        font,
                        e.Bounds,
                        foreColor,
                        TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter
                        );
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        /// <summary>
        /// DataGridViewの初期設定
        /// </summary>
        private void InitDataGridView()
        {
            try
            {
                int colCount = 20;
                int rowCount = 20;
                int cellWidth = 20;

                this.dataGridView1.ReadOnly = true;
                this.dataGridView1.AllowUserToAddRows = false;
                this.dataGridView1.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
                this.dataGridView1.RowHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
                this.dataGridView1.RowHeadersWidth = 50;

                //列追加
                var cols = Enumerable.Range(0, colCount).Select(idx =>
                 new DataGridViewTextBoxColumn
                 {
                     Name = idx.ToString(),
                     Width = cellWidth,
                 });

                this.dataGridView1.Columns.AddRange(cols.ToArray());

                //行追加
                foreach (int rowIdx in Enumerable.Range(0, rowCount))
                {
                    this.dataGridView1.Rows.Add(this.RandomString(rowCount).Cast<object>().ToArray());
                    this.dataGridView1.Rows[rowIdx].HeaderCell.Value = rowIdx.ToString();
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// ランダムな文字列を作成します。
        /// </summary>
        /// <param name="length">文字列長</param>
        /// <returns></returns>
        public string RandomString(int length)
        {
            const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            return new string(Enumerable.Repeat(chars, length)
                .Select(s => s[this._Random.Next(s.Length)]).ToArray());
        }
    }
}
```

#まとめ
仕事でDataGridViewのToolTipのフォントサイズを変更したくて方法を調べました。
CellToolTipTextNeededイベントを使用する方法が見つかりましたが、DataSourceプロパティが設定されているか、VirtualModeプロパティがtrueの場合にのみこのイベントが発生するようです。
なので今回の例のようにDataGridViewに直接行列を追加している場合はイベントが発生しませんでした。

試行錯誤していて、DataGridViewのちらつきを防止するときに使用するDoubleBufferedにアクセスする方法と同じ方法でDataGridViewのToolTipにアクセスできるのではないか？と思いつき、今回の方法を考え付きました。