DataGridViewに複数行で結合した列の表題を表示する

DataGridView に、2 行 3 列で、1 行目が結合した以下のような表題を表示してみます。DataGridViewのCellPaintingイベントで、罫線や文字列を描画します。

+--------------------------------------+
| 結合された1行目の表題                 |
+------------+------------+------------+
| 1列目の表題 | 2列目の表題 | 3列目の表題|
+------------+------------+------------+


Form には dataGridView1 という名前の DataGridView を配置しておきます。次に示すのは Form のコンストラクタです。ここでは、

  • DataGridView に 3 列追加します。
  • 列の表題の高さを自動調整しないように設定します。
  • CellPainting イベントハンドラを設定します。
public Form1()
{
    // Form には dataGridView1 という名前の DataGridView を配置しておきます。
    InitializeComponent();

    // DataGridView に 3 列追加します。
    DataGridViewTextBoxColumn[] columns =
    {
        new DataGridViewTextBoxColumn() {HeaderText = "1列目の表題"},
        new DataGridViewTextBoxColumn() {HeaderText = "2列目の表題"},
        new DataGridViewTextBoxColumn() {HeaderText = "3列目の表題"}
    };
    dataGridView1.Columns.AddRange(columns);

    // 列の表題の高さを自動調整しないように設定します。
    dataGridView1.ColumnHeadersHeightSizeMode =
        DataGridViewColumnHeadersHeightSizeMode.EnableResizing;
    dataGridView1.ColumnHeadersHeight = 50;

    // CellPainting イベントハンドラを設定します。
    dataGridView1.CellPainting += CellPainting;
}


CellPainting イベントハンドラの最初の部分です。描画する表題セル以外はここで戻り、通常通り DataGridView に描画してもらいます。

/// <summary>
/// セルの描画が必要な場面で呼び出されるイベント。
/// </summary>
private void CellPainting(Object sender, DataGridViewCellPaintingEventArgs e)
{
    // 描画する表題セル以外はここで戻り、通常通り DataGridView に描画してもらう。
    if (e.RowIndex != -1 || e.ColumnIndex < 0)
    {
        return;
    }


ここからは、表示を変更する表題セルを描画してゆきます。まずは、結合させる一番左のセルの左側の X 座標と、一番右のセルの右側の X 座標を求めます。

    // 結合させる一番左のセルの左側の X 座標を求める。
    int mergedLeftX = e.CellBounds.X;
    for (int li = e.ColumnIndex - 1; li >= 0; --li)
    {
	DataGridViewColumn col = dataGridView1.Columns[li];
	mergedLeftX -= col.Width;
    }

    // 結合させる一番右のセルの右側の X 座標を求める。
    int mergedRightX = e.CellBounds.X + e.CellBounds.Width - 1;
    for (int ri = e.ColumnIndex + 1; ri < dataGridView1.Columns.Count; ++ri)
    {
	DataGridViewColumn col = dataGridView1.Columns[ri];
	mergedRightX += col.Width;
    }


求めた座標を使って、2つのRectangleを作成します。

  • 結合させるセルの 1 行目の範囲 (3 つのセルの上半分) の長方形
  • ペイントするセルの 2 行目の範囲 (このセルの下半分) の長方形
    // 結合させるセルの 1 行目の範囲 (3 つのセルの上半分) の長方形を作成する。
    int mergedWidth = mergedRightX - mergedLeftX + 1;
    int firstLineHeight = e.CellBounds.Height / 2;
    Rectangle mergedFirstLineRect = new Rectangle(
	mergedLeftX - 1, e.CellBounds.Y, mergedWidth, firstLineHeight);

    // ペイントするセルの 2 行目の範囲 (このセルの下半分) の長方形を作成する。
    int secondLineHeight = e.CellBounds.Height - firstLineHeight;
    Rectangle secondLineRect = new Rectangle(
	e.CellBounds.X - 1, e.CellBounds.Y + firstLineHeight, 
	e.CellBounds.Width, secondLineHeight - 1);


作成した長方形を背景色で塗りつぶし、境界の線を引きます。

    // 作成した長方形を背景色で塗りつぶす。
    Brush bgBrush = new SolidBrush(e.CellStyle.BackColor);
    e.Graphics.FillRectangle(bgBrush, mergedFirstLineRect);
    e.Graphics.FillRectangle(bgBrush, secondLineRect);

    // 境界の線を引く。
    Color borderColor = dataGridView1.GridColor;
    Pen borderPen = new Pen(borderColor);
    e.Graphics.DrawRectangle(borderPen, mergedFirstLineRect);
    e.Graphics.DrawRectangle(borderPen, secondLineRect);


文字列を左揃え、上下方向は真ん中、左右のパッドあり、で描きます。

    // 文字列を左揃え、上下方向は真ん中、左右のパッドあり、で描く。
    String firstLineText = "結合された1行目の表題";
    String secondLineText = e.FormattedValue.ToString();
    Font font = e.CellStyle.Font;
    Color fgColor = e.CellStyle.ForeColor;
    TextFormatFlags flags = 
	TextFormatFlags.Left | TextFormatFlags.VerticalCenter 
	| TextFormatFlags.LeftAndRightPadding;
    TextRenderer.DrawText(
	e.Graphics, firstLineText, font, mergedFirstLineRect, fgColor, flags);
    TextRenderer.DrawText(
	e.Graphics, secondLineText, font, secondLineRect, fgColor, flags);


最後に e.Handled を true に設定し、DataGridView が描画しないようにします。

    // 描画したことを示す。DataGridView は描画しない。
    e.Handled = true;
}


以下のような課題があります。

  • 結合した列の文字列は左揃えのみ -- 列の幅が変更された場合、幅が変更された列とその右側の列が再描画されるようです。幅が変更された列より左側は、表示が変わらないため再描画されません。結合した列の文字列が中央揃えや右揃えの場合、列幅変更の影響を受けない部分が再描画されず、表示が乱れます。
  • 表題の高さの自動調整 -- 列幅を変更しても表題の高さは自動調整されません。
  • 列のソート順のグリフ表示など -- このプログラムでは描画しておらず、必要ならば描画するプログラムの作成が必要です。