【入門編】Windows FormsのDataGridViewにおけるセル結合(CellMerge)のエミュレーション:CellPaintingイベントを駆使したエクセルライクなヘッダー・明細の表現 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク
スポンサーリンク

【極限の知見】DataGridViewで「セル結合」を完全エミュレートする:CellPaintingによる芸術的なUI設計

「Excelのように、同じ値が続くセルを一つにまとめて表示したい……」

Windows Forms(WinForms)で業務アプリケーションを開発していると、必ずと言っていいほど直面する壁があります。それが、DataGridViewにおける「セル結合」の不在です。

標準のDataGridViewは、データベースのレコード(行)とカラム(列)を忠実に、1対1の格子状に表示するためのコントロールです。構造上、セルを物理的に「結合」する機能は持っていません。しかし、ユーザーは「見た目の美しさ」や「情報の整理」のために、エクセルのような結合されたレイアウトを求めます。

今日は、標準機能の限界を超え、`CellPainting`イベントを駆使して「セル結合」を視覚的にエミュレートする(作り出す)高度なテクニックを伝授しましょう。

ここをクリアすれば、あなたは単なる「マクロの記録係」を卒業し、プロフェッショナルなUIエンジニアへの第一歩を踏み出すことになりますよ。

1. 本質を理解する:物理的な結合ではなく「視覚的な錯覚」を作る

まず、設計思想として最も重要なことをお伝えします。

プロのエンジニアは、DataGridViewのセルを物理的に結合しようとはしません。なぜなら、セルを結合してしまうと、データのインデックス(何行目の何列目か)が狂い、データの更新やソートといった基本動作が崩壊してしまうからです。

私たちが目指すのは、「データ構造は格子のまま維持しつつ、描画(ペイント)の段階で、境界線を消して一つの大きなセルに見せる」という手法です。これを「視覚的エミュレーション」と呼びます。

実装のロジック

1. 現在のセルと、その「上のセル」の値を比較する。
2. もし値が同じなら、「上のセルの境界線」を消し、「現在のセルのテキスト」を描画しない。
3. これにより、ユーザーの目には一つの大きなセルが繋がっているように見える。

2. 実装の核心:CellPaintingイベントの攻略

この魔法をかける場所が、`CellPainting`イベントです。このイベントは、DataGridViewが「このセルをどう描画すべきか?」と迷った時に呼び出される、描画の最終防衛ラインです。

実装コード例 (VB.NET)

以下のコードは、特定の列(例:カテゴリ列)において、上下のセルが同じ値であれば結合しているように見せる実装例です。

''' <summary>
''' DataGridViewの描画イベントハンドラ
''' </summary>
''' <param name="sender">イベントの発生源</param>
''' <param name="e">描画に関する詳細情報を含むイベント引数</param>
Private Sub DataGridView1_CellPainting(sender As Object, e As DataGridViewCellPaintingEventArgs) Handles DataGridView1.CellPainting
    ' 1. ヘッダーや、データが存在しない行(インデックスが負)は除外する
    If e.RowIndex < 0 OrElse e.ColumnIndex < 0 Then Return

    ' 2. 結合を適用したい特定の列を指定する(例:ColumnIndex = 1 の列)
    ' 全ての列に適用すると重くなるため、ターゲットを絞るのがプロの鉄則です
    If e.ColumnIndex = 1 Then
        
        ' 3. 「現在のセル」と「一つ上のセル」の値を比較する
        Dim currentValue As Object = DataGridView1.Rows(e.RowIndex).Cells(e.ColumnIndex).Value
        Dim previousValue As Object = Nothing

        ' 一つ上の行が存在するかチェック
        If e.RowIndex > 0 Then
            previousValue = DataGridView1.Rows(e.RowIndex - 1).Cells(e.ColumnIndex).Value
        End If

        ' 4. 値が同じである場合、結合エミュレーションを実行
        If currentValue IsNot Nothing AndAlso previousValue IsNot Nothing AndAlso currentValue.ToString() = previousValue.ToString() Then
            
            ' --- ここからが描画のカスタマイズ ---

            ' 背景を描画する(これを行わないと、セルの色が消えてしまいます)
            e.PaintBackground(e.CellBounds, True)

            ' セルの「上側の境界線」を描画しないようにすることで、結合しているように見せる
            ' e.CellBounds はセルの領域を表す Rectangle です
            Dim rect As Rectangle = e.CellBounds
            rect.Y += rect.Height / 2 ' 境界線を中央付近に持ってくる(あるいは完全に消すなら調整)
            
            ' 今回は「上の境界線を引かない」というシンプルな手法をとります
            ' 境界線を描画する代わりに、セルの枠線の一部をスキップするロジック
            
            ' 下側の境界線だけを描画し、上側を描画しない
            Using p As Pen = New Pen(DataGridView1.GridColor)
                ' 上側の線は描かない
                ' 下側の線だけを描画
                e.Graphics.DrawLine(p, rect.Left, rect.Bottom - 1, rect.Right, rect.Bottom - 1)
            End Using

            ' テキストは描画しない(上のセルですでに描画されているため)
            
            ' 5. 「このセルの描画は私が完了しました」とシステムに伝える
            ' これを忘れると、標準の描画が重なってしまい、表示が崩れます
            e.Handled = True
        End If
    End If
End Sub

3. 陥りやすい罠と、エンジニアとしての「重み」

このテクニックを使いこなすには、単にコードを書くだけでは足りません。現場で必ず直面する「副作用」への対策を知っておく必要があります。

① 画面のチラつき(Flicker)問題

`CellPainting`で複雑な描画を行うと、スクロールした瞬間に画面が激しく点滅することがあります。これは、描画の更新が追いついていないためです。

【解決策】
DataGridViewのプロパティである `DoubleBuffered` を `True` に設定してください。
※ただし、`DoubleBuffered` は `protected` プロパティなので、少し工夫が必要です。

' フォームのLoadイベントなどで実行
Public Sub EnableDoubleBuffered(ByVal dgv As DataGridView)
    ' リフレクションを使用して、隠されたDoubleBufferedプロパティを強制的にTrueにする
    Dim dgvType As Type = dgv.GetType()
    Dim pi As Reflection.PropertyInfo = dgvType.GetProperty("DoubleBuffered", 
        Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic)
    pi.SetValue(dgv, True, Nothing)
End Sub

② `e.Handled = True` の重要性

ここが初学者が最も間違えやすいポイントです。
`e.Handled = True` とは、「このセルの描画に関する責任は、私がすべて引き受けました。OS(システム)側は余計なことはしないでください」という宣言です。
これを忘れると、あなたがせっかく描いた「結合された見た目」の上に、システムが「標準の枠線」と「テキスト」を上書きしてしまい、結局普通のセルに戻ってしまいます。

③ パフォーマンスの考慮

`CellPainting` は、画面上のセルが描画されるたびに、凄まじい頻度で呼び出されます。

  • `If` 文による条件分岐は最小限にする。
  • ループの中で重い計算(データベースへの問い合わせなど)を絶対に行わない。
  • `Pen` や `Brush` オブジェクトを毎回 `New` するのではなく、使い回す(または `Using` で確実に解放する)。

これらを意識できるかどうかが、単なる「動くコード」を書く人と、「堅牢なシステム」を作る人の分かれ目です。

まとめ:ここをクリアすれば、基本はバッチリですよ

DataGridViewのセル結合エミュレーションは、「データの整合性」と「ユーザー体験(UX)」の高度な妥協点を見つける作業です。

1. 物理的な結合はせず、描画だけで見せる。
2. `CellPainting` で、境界線とテキストの描画をコントロールする。
3. `e.Handled = True` で描画の主導権を握る。
4. `DoubleBuffered` でパフォーマンス(滑らかさ)を担保する。

このステップをマスターすれば、あなたはWindows FormsにおけるUI制御の深淵に一歩近づきました。最初は難しく感じるかもしれませんが、一度仕組みを理解してしまえば、これほど自由度の高い武器はありません。

さあ、恐れずにコードを書いてみてください。エラーが出たら、それはあなたが新しい知識を手に入れるための「招待状」です。応援していますよ!

タイトルとURLをコピーしました