フォームに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の領域のへ表示するテキストを描画する。
近々コードを再アップするので参考にしていただければと思います。
このコメントは投稿者によって削除されました。
返信削除