フォームにDataGridViewを配置しDataGridViewのPaintイベントで列ヘッダーを2段表示にする。
フォームにDataGrisViewを配置し列を追加します。
次にDataGridViewのPaintイベントで以下の手順で列ヘッダーに指定された行数を描画します。
- 列ヘッダーの行数(段数)、行の高さの変数を宣言。
- 列ヘッダーセル定義構造を以下の様に宣言。
行
列
結合する列数
結合する行数
セルに関連付けられたテキスト - 列ヘッダーセル定義構造体配列を宣言。
- フォームのロード時
ちらつき防止のためのダブルバッファリング処理。
ColumnHeadersHeightSizeModeを EnableResizingに設定。
列ヘッダーの行数に合わせてColumnHeadersHeightを設定。 - DataGridViewのPaint
行数と高さに基づいて各列ヘッダーに行数分の四角形を描画します。
最下行にその列のヘッダーテキストを描画します。
ヘッダーセル定義に基づいてセルの結合を描画します。 - 列の幅が変更時、スクロール時、コントロールのサイズ変更時
直前の描画が残らないように列ヘッダーの描画領域を無効化します。
Public Class Form1
''' <summary>
''' 列ヘッダーの行数
''' </summary>
''' <remarks></remarks>
Private ColumnHeaderRowCount As Integer = 2
''' <summary>
''' 列ヘッダーの行の高さ
''' </summary>
''' <remarks></remarks>
Private columnHeaderrRowHeight As Integer = 17
''' <summary>
''' 列ヘッダーセル定義構造体
''' </summary>
''' <remarks></remarks>
Public Structure HeaderCell
Public Row As Integer
Public Column As Integer
Public RowSpan As Integer
Public ColumnSpan As Integer
Public Text As String
''' <summary>
''' 列ヘッダーセル定義
''' </summary>
''' <param name="paramRow">行</param>
''' <param name="paramColumn">列</param>
''' <param name="paramRowSpan">結合する行数</param>
''' <param name="paramColumnSpan">結合する列数</param>
''' <param name="paramText">セルに関連付けられたテキスト</param>
''' <remarks></remarks>
Sub New(ByVal paramRow As Integer, ByVal paramColumn As Integer, ByVal paramRowSpan As Integer, ByVal paramColumnSpan As Integer, ByVal paramText As String)
' TODO: Complete member initialization
Row = paramRow
Column = paramColumn
RowSpan = paramRowSpan
ColumnSpan = paramColumnSpan
Text = paramText
End Sub
End Structure
''' <summary>
''' 列ヘッダーセル定義
''' </summary>
''' <remarks></remarks>
Public HeaderCells As HeaderCell() = {New HeaderCell(0, 0, 1, 2, "Category1"), _
New HeaderCell(0, 2, 1, 2, "Category2"), _
New HeaderCell(0, 4, 2, 1, "Column5")}
''' <summary>
''' フォームを読み込む時
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'ちらつき防止
Dim myType As Type = GetType(DataGridView)
Dim myPropInfo As System.Reflection.PropertyInfo = myType.GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic)
myPropInfo.SetValue(Me.DataGridView1, True, Nothing)
'列ヘッダーの高さの調整モード
Me.DataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.EnableResizing
'列ヘッダーの高さを行数に合わせる
Me.DataGridView1.ColumnHeadersHeight = columnHeaderrRowHeight * ColumnHeaderRowCount
End Sub
''' <summary>
''' コントロールを再描画する時
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub DataGridView1_Paint(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.PaintEventArgs) Handles DataGridView1.Paint
'列が無い場合
If Me.DataGridView1.ColumnCount = 0 Then
Exit Sub
End If
'行が無い場合
If Me.DataGridView1.RowCount = 0 Then
Exit Sub
End If
'列ヘッダーの行の高さの取得
Dim rowHeight As Integer = Me.DataGridView1.ColumnHeadersHeight / ColumnHeaderRowCount
Dim lineWidth As Integer = 1
'列ヘッダーを指定された行数にセル表示する
For columuns = 0 To Me.DataGridView1.ColumnCount - 1
For rows = 0 To ColumnHeaderRowCount - 1
'列ヘッダーの表示領域の取得
Dim rect As Rectangle = Me.DataGridView1.GetCellDisplayRectangle(columuns, -1, True)
''列ヘッダーの描画領域の底部の座標を保存
Dim btm As Integer = rect.Bottom
'セルの描画領域のY座標
Select Case Me.DataGridView1.BorderStyle
Case Windows.Forms.BorderStyle.None
rect.Y = rowHeight * rows
Case Windows.Forms.BorderStyle.FixedSingle
rect.Y = rowHeight * rows + lineWidth
Case Windows.Forms.BorderStyle.Fixed3D
rect.Y = rowHeight * rows + (lineWidth * 2)
End Select
'セルの描画領域のX座標
rect.X -= lineWidth
'セルの描画領域の高さ
rect.Height = rowHeight
'最下行の場合高さを調整
If rows = Me.ColumnHeaderRowCount - 1 Then
rect.Height = btm - rect.Y - lineWidth
End If
Dim gridPen As New Pen(Me.DataGridView1.GridColor)
e.Graphics.DrawRectangle(gridPen, rect)
'セルの背景色の領域
rect.Y += lineWidth
rect.X += lineWidth
rect.Height -= lineWidth
rect.Width -= lineWidth
'背景色
Dim backBrash As New SolidBrush(Me.DataGridView1.BackColor)
e.Graphics.FillRectangle(backBrash, rect)
'見出しを最下列に表示
If rows = Me.ColumnHeaderRowCount - 1 Then
Dim text As String = Me.DataGridView1.Columns(columuns).HeaderText
Dim formatFlg As TextFormatFlags = GetTextFormatFlags(Me.DataGridView1.ColumnHeadersDefaultCellStyle.Alignment)
TextRenderer.DrawText(e.Graphics, text, Me.DataGridView1.ColumnHeadersDefaultCellStyle.Font, _
rect, Me.DataGridView1.ColumnHeadersDefaultCellStyle.ForeColor, _
formatFlg)
End If
'リソースの解放
gridPen.Dispose()
backBrash.Dispose()
Next
Next
'列ヘッダーセル定義の処理
For i = 0 To Me.HeaderCells.Count - 1
'セルの結合の開始行がヘッダーの行数より大きい場合は除外
If HeaderCells(i).Row > Me.ColumnHeaderRowCount - 1 Then
Continue For
End If
'セルの結合の開始列の列インデックスが列数より大きい場合は除外
If HeaderCells(i).Column > Me.DataGridView1.ColumnCount - 1 Then
Continue For
End If
'描画領域の設定
Dim rect As Rectangle = Nothing
'結合する列中のソート状態
Dim sortText As String = String.Empty
'結合するセルの幅の取得
For j = Me.HeaderCells(i).Column To Me.HeaderCells(i).Column + Me.HeaderCells(i).ColumnSpan - 1
If Me.DataGridView1.Columns(j).Displayed = False Then
Continue For
End If
If rect = Nothing Then
rect = Me.DataGridView1.GetCellDisplayRectangle(j, -1, True)
Else
rect.Width += Me.DataGridView1.GetCellDisplayRectangle(j, -1, True).Width
End If
Next
'結合するセルが画面中に無い場合
If rect = Nothing Then
Continue For
End If
'結合する行がヘッダー行数より大きい場合
Dim rowSapn As Integer = Me.HeaderCells(i).RowSpan
If rowSapn > ColumnHeaderRowCount Then
rowSapn = ColumnHeaderRowCount
End If
'列ヘッダーの描画領域の底部の座標を保存
Dim btm As Integer = rect.Bottom
'結合するセルの描画領域のY座標
Select Case Me.DataGridView1.BorderStyle
Case Windows.Forms.BorderStyle.None
rect.Y = rowHeight * (Me.HeaderCells(i).Row)
Case Windows.Forms.BorderStyle.FixedSingle
rect.Y = rowHeight * (Me.HeaderCells(i).Row) + lineWidth
Case Windows.Forms.BorderStyle.Fixed3D
rect.Y = rowHeight * (Me.HeaderCells(i).Row) + (lineWidth * 2)
End Select
'結合するセルの描画領域のX座標
rect.X -= lineWidth
'結合するセルの描画領域の高さ
rect.Height = rowHeight * rowSapn
'最下行の場合は描画領域の高さを調整する
If Me.HeaderCells(i).Row + rowSapn = Me.ColumnHeaderRowCount Then
rect.Height = btm - rect.Y - lineWidth
End If
'グッリドの線
Dim gridPen As New Pen(Me.DataGridView1.GridColor)
'背景色の取得
Dim backgroundColor As System.Drawing.Color = Me.DataGridView1.ColumnHeadersDefaultCellStyle.BackColor
'背景色
Dim backBrash As New SolidBrush(backgroundColor)
'くぼみ線
Dim whiteBrash As New SolidBrush(Color.White)
'枠線の描画
e.Graphics.DrawRectangle(gridPen, rect)
'結合セルの背景色の描画領域の設定
rect.Y += lineWidth
rect.X += lineWidth
rect.Height -= lineWidth
rect.Width -= lineWidth
'背景色の描画
e.Graphics.FillRectangle(backBrash, rect)
'テキストの描画
Dim foreColor As System.Drawing.Color = Me.DataGridView1.ColumnHeadersDefaultCellStyle.ForeColor
Dim formatFlg As TextFormatFlags = GetTextFormatFlags(Me.DataGridView1.ColumnHeadersDefaultCellStyle.Alignment)
TextRenderer.DrawText(e.Graphics, Me.HeaderCells(i).Text & sortText, Me.DataGridView1.ColumnHeadersDefaultCellStyle.Font, _
rect, foreColor, formatFlg)
'リソースの解放
gridPen.Dispose()
backBrash.Dispose()
whiteBrash.Dispose()
Next
End Sub
''' <summary>
''' 結合元のセルの文字位置から結合後の文字位置を取得する
''' </summary>
''' <param name="alignment">テキストの配置</param>
''' <remarks></remarks>
Private Function GetTextFormatFlags(ByVal alignment As DataGridViewContentAlignment) As TextFormatFlags
Try
''文字の描画
Dim formatFlg As TextFormatFlags = TextFormatFlags.Right Or TextFormatFlags.VerticalCenter Or TextFormatFlags.EndEllipsis
'表示位置
Select Case alignment
Case DataGridViewContentAlignment.BottomCenter
formatFlg = TextFormatFlags.Bottom Or TextFormatFlags.HorizontalCenter Or TextFormatFlags.EndEllipsis
Case DataGridViewContentAlignment.BottomLeft
formatFlg = TextFormatFlags.Bottom Or TextFormatFlags.Left Or TextFormatFlags.EndEllipsis
Case DataGridViewContentAlignment.BottomRight
formatFlg = TextFormatFlags.Bottom Or TextFormatFlags.Right Or TextFormatFlags.EndEllipsis
Case DataGridViewContentAlignment.MiddleCenter
formatFlg = TextFormatFlags.VerticalCenter Or TextFormatFlags.HorizontalCenter Or TextFormatFlags.EndEllipsis
Case DataGridViewContentAlignment.MiddleLeft
formatFlg = TextFormatFlags.VerticalCenter Or TextFormatFlags.Left Or TextFormatFlags.EndEllipsis
Case DataGridViewContentAlignment.MiddleRight
formatFlg = TextFormatFlags.VerticalCenter Or TextFormatFlags.Right Or TextFormatFlags.EndEllipsis
Case DataGridViewContentAlignment.TopCenter
formatFlg = TextFormatFlags.Top Or TextFormatFlags.HorizontalCenter Or TextFormatFlags.EndEllipsis
Case DataGridViewContentAlignment.TopLeft
formatFlg = TextFormatFlags.Top Or TextFormatFlags.Left Or TextFormatFlags.EndEllipsis
Case DataGridViewContentAlignment.TopRight
formatFlg = TextFormatFlags.Top Or TextFormatFlags.Right Or TextFormatFlags.EndEllipsis
End Select
Return formatFlg
Catch ex As Exception
Throw
End Try
End Function
''' <summary>
''' 列ヘッダーの描画領域の無効化
''' </summary>
''' <remarks></remarks>
Private Sub InvalidateUnitColumns()
Try
If Me.DataGridView1.RowCount > 0 Then
Dim hRect As Rectangle = Me.DataGridView1.DisplayRectangle
'hRect.Height = Me.DataGridView1.ColumnHeadersHeight
Me.DataGridView1.Invalidate(hRect)
End If
Catch ex As Exception
Throw
End Try
End Sub
''' <summary>
''' 列の幅が変更された時
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub DataGridView1_ColumnWidthChanged(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewColumnEventArgs) Handles DataGridView1.ColumnWidthChanged
InvalidateUnitColumns()
End Sub
''' <summary>
''' スクロールする時
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub DataGridView1_Scroll(ByVal sender As System.Object, ByVal e As System.Windows.Forms.ScrollEventArgs) Handles DataGridView1.Scroll
InvalidateUnitColumns()
End Sub
''' <summary>
''' コントロールのサイズが変更された時
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub DataGridView1_SizeChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles DataGridView1.SizeChanged
InvalidateUnitColumns()
End Sub
''' <summary>
''' マウスのボタンが押された時
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub DataGridView1_MouseDown(sender As System.Object, e As System.Windows.Forms.MouseEventArgs) Handles DataGridView1.MouseDown
Try
''列幅、行高を調整するドラグ線を見えるようにするためにダブルバッファを解除する
'ちらつき防止
Dim myType As Type = GetType(DataGridView)
Dim myPropInfo As System.Reflection.PropertyInfo = myType.GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic)
myPropInfo.SetValue(Me.DataGridView1, False, Nothing)
Catch ex As Exception
MessageBox.Show(ex.ToString)
End Try
End Sub
''' <summary>
''' マウスのボタンが離された時
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub DataGridView1_MouseUp(sender As System.Object, e As System.Windows.Forms.MouseEventArgs) Handles DataGridView1.MouseUp
Try
''OnMouseDownイベントで解除されたダブルバッファを適用する
Dim myType As Type = GetType(DataGridView)
Dim myPropInfo As System.Reflection.PropertyInfo = myType.GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic)
myPropInfo.SetValue(Me.DataGridView1, True, Nothing)
Catch ex As Exception
MessageBox.Show(ex.ToString)
End Try
End Sub
End Class
![]() |
| 実行結果 |
ただ四角形と文字を描画しているだけなので結合されたセル上でマウスクリックされた場合は
その直下の列がソートされます。またセルにマウスエンターされてもハイライトはされません。


参考にしようと思ったけど、つづり間違い多いな・・・
返信削除ご意見ありがとうございます。
削除以下4件のつづり間違えを確認しましたので訂正しました。
誤 → 正
・Categori → Category
・Hight → height
・rowHieght → rowHeight
・backGroundColor → backgroundColor
すばらしいサンプルをありがとうございます。
返信削除他のサイトにはないきめ細かな制御(ちらつき防止やBorderStyleの違いによる枠の処理)まで示していただき、助かりました。
このサンプルをフルに活用させていただきます。
本当に感謝です!
コメントありがとうございます。
削除こちらこそこのサンプルが活用されお役に立てれば大変嬉しいことです。
このコメントはブログの管理者によって削除されました。
返信削除了解です。お役に立てると良いのですが。
削除素晴らしい!そのまま使えました。感謝しかありません。
返信削除ありがとうございました。
返信遅くなりました。
削除お役に立てて何よりです。
参考にさせて頂きました。
返信削除DataGridView1.Columns(2).DividerWidth = 3
の様に一部の区切り線を太くした場合にヘッダーの区切り線も太くしたいです。
セルの背景色の領域設定時にrect.Widthを区切り線の太さ分狭くしてみましたが、
思ったように描画されませんでした。
区切り線の対応についてアドバイス頂けませんか。
しばらくサボっていたため返信できずにすみません。
削除もし自己解決していれば幸いなのですが…
で区切り線の対処としまして当初区切り線に合わせてヘッダーにも区切り線を描画すれば良いのかな
と考えたのですが、意外に横スクロール等の場面で線の位置がずれたりと上手く行きませんでした。
そこで以下の方法で分割線への対応ができるのではないか考えます。
1 セルの表示領域えお得るGetCellDisplayRectangleの3番目の引数をfalse(セル境界全体を取得)に変更。
2 取得した領域にの区切り線の描画色で塗りつぶしの四角形を描画
3 2の領域から区切り線の太さ分縮小したセルの背景色を描画(区切り線を太くする場合はその領域の幅を短く)
4 3の領域のへ表示するテキストを描画する。
近々コードを再アップするので参考にしていただければと思います。
このコメントは投稿者によって削除されました。
返信削除