2013年8月8日木曜日

【VB.NET】DataGridViewの列ヘッダーの複数行表示するカスタムコントロール②


前回の記事で作成したCustomDataGridViewの使用例です。

プロジェクトにフォームを追加しツールボックスからCustomDataGridViewをフォームに配置します。

先ずはColumnHeaderHeigthSizeModeプロパティをAutoSize以外に設定します。
次に新たに追加したプロパティを設定します。

HeaderCellプロパティをクリックするとHeaderCellコレクションエディターが開くので
HeaderCellを追加設定します。
OKボタンをクリックするとHeaderCellの設定がフォームデザイナーのCustomDataGridViewに
反映されます。

実行結果

HeaderCellは並び順に上書きされます。


またフォームにCustomDataGridviewを配置しコードからプロパティの設定もできます。



Imports System.Reflection
Imports System.Windows.Forms.ComboBox

Public Class Form1
    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load

        'サンプルデータテーブル
        Dim dt As New DataTable
        dt.Columns.Add("NO", Type.GetType("System.Double"))
        dt.Columns.Add("CATEGORY1", Type.GetType("System.String"))
        dt.Columns.Add("CATEGORY2", Type.GetType("System.String"))
        dt.Columns.Add("CATEGORY3", Type.GetType("System.String"))
        dt.Columns.Add("NAME", Type.GetType("System.String"))
        dt.Columns.Add("VAL1", Type.GetType("System.Double"))
        dt.Columns.Add("VAL2", Type.GetType("System.Double"))
        dt.Columns.Add("VAL3", Type.GetType("System.Double"))
        dt.Columns.Add("VAL4", Type.GetType("System.Double"))
        dt.Columns.Add("VAL5", Type.GetType("System.Double"))

        Dim r As New System.Random(1000)

        Dim no As Integer = 0
        For j1 = 0 To 100
            For j2 = 0 To 5
                For j3 = 0 To 3
                    Dim nr As DataRow = dt.NewRow
                    nr("NO") = no
                    nr("CATEGORY1") = "分類" & j1.ToString("00")
                    nr("CATEGORY2") = "分類" & j2.ToString("00")
                    nr("CATEGORY3") = "分類" & j3.ToString("00")
                    nr("NAME") = "名前" & j1.ToString("00") & j2.ToString("00") & j3.ToString("00")
                    nr("VAL1") = r.Next(10000)
                    nr("VAL2") = r.Next(10000)
                    nr("VAL3") = r.Next(10000)
                    nr("VAL4") = r.Next(10000)
                    nr("VAL5") = r.Next(10000)
                    dt.Rows.Add(nr)

                    no += 1
                Next
            Next
        Next

        
        'CustomHeaderDataGridView1の設定
        Me.CustomHeaderDataGridView1.DataSource = dt

        Me.CustomHeaderDataGridView1.ColumnHeaderBorderStyle = CustomHeaderDataGridView.HeaderCellBorderStyle.DoubleLine
        Me.CustomHeaderDataGridView1.ColumnHeaderRowCount = 2
        Me.CustomHeaderDataGridView1.ColumnHeaderRowHeight = 17

        'ヘッダーセル定義
        Dim cells As New HeaderCell
        cells.Column = 0
        cells.Row = 0
        cells.ColumnSpan = 1
        cells.RowSpan = 2
        cells.SortVisible = True
        cells.Text = "NO"
        cells.TextAlign = DataGridViewContentAlignment.MiddleCenter
        Me.CustomHeaderDataGridView1.HeaderCells.Add(cells)

        cells = New HeaderCell
        cells.Column = 1
        cells.Row = 0
        cells.ColumnSpan = 3
        cells.RowSpan = 1
        cells.SortVisible = True
        cells.Text = "CATEGORY"
        cells.TextAlign = DataGridViewContentAlignment.MiddleCenter
        Me.CustomHeaderDataGridView1.HeaderCells.Add(cells)

        cells = New HeaderCell
        cells.Column = 4
        cells.Row = 0
        cells.ColumnSpan = 1
        cells.RowSpan = 2
        cells.SortVisible = True
        cells.Text = "NAME"
        cells.TextAlign = DataGridViewContentAlignment.MiddleCenter
        Me.CustomHeaderDataGridView1.HeaderCells.Add(cells)

        cells = New HeaderCell
        cells.Column = 5
        cells.Row = 0
        cells.ColumnSpan = 5
        cells.RowSpan = 1
        cells.SortVisible = True
        cells.Text = "VAL"
        cells.TextAlign = DataGridViewContentAlignment.MiddleCenter
        Me.CustomHeaderDataGridView1.HeaderCells.Add(cells)

        'ヘッダーサイズモード
        Me.CustomHeaderDataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.EnableResizing
    End Sub
End Class
実行結果
(注意点) 
ColumnHeadersHeightSizeModeは手動でAutoSize以外に変更する必要があります。
四角形と文字を描画しているだけなので結合されたセル上でマウスクリックされた場合はその直下の列がソートされます。

51 件のコメント:

  1. やりたいとマッチしているので、こちらを利用させて頂いているのですが、
    新規行の追加を禁止したく、AllowUserToAddRowsをFalseにした所、カスタマイズで追加した部分が消えてしまいます。
    新規行の追加を禁止しつつ、ヘッダーを複数行に設定する方法はありませんでしょうか?
    答えまででなくてもよいので、解決に向かえるヒントだけでも頂けると幸いです。

    返信削除
    返信
    1. 度々申し訳ございません。
      自己解決しましたので、ご報告いたします。
      CustomHeaderDataGridViewクラスの中で、RowCountが0の時の判定を削除する事で対応できました。(215、494行目)
      特に問題なく動作しているようですが、この対応で気になる事等ございましたら、ご連絡頂けると幸いです。

      削除
    2. ご指摘ありがとうございます。
      確かに新規行の追加を禁止した場合に列ヘッダーのカスタマイズ部分が表示されません。
      RowCountが0の時の判定を削除の対応で問題ないと思います。
      そもそもこの判定は必要ないですね。
      もともとヘッダーではなくデータの1レコードを多段で表示できないかと考えていたのですが、その時の
      名残でした。
      近いうちにコードを修正してアップし直したいと思います。

      削除
    3. ご回答ありがとうございます。
      助かりました。
      今後も参考にさせて頂きます。

      削除
    4. 上記の件、本日修正しました。

      削除
  2. ヘッダーを2行表示ができるDataGridViewを検索して、こちらのページにたどり着き
    使用させていただいております。

    使用してて困ったことが起こりました。
    プロパティの「BorderStyle」を「None」に設定し、スクロールをした時、
    セルの境界線が太くなります。

    太くならないようにするには、どのような対応をすればよろしいでしょうか。
    よろしくお願いいたします。

    返信削除
    返信
    1. ご指摘ありがとうございます。
      枠線がない分描画領域が列ヘッダよりはみ出したものと思います。

      ひとまずのた対処としてコードの
      Private Sub InvalidateUnitColumns()中のhRect.Height = Me.DataGridView1.ColumnHeadersHeightをhRect.Height = Me.DataGridView1.ColumnHeadersHeight+1にして無効化する領域を広げてみてはどうでしょうか。

      後日改めてBorderstyleの変更に対処をお示ししたいと思います。

      削除
    2. 上記の対処法不十分でした‥
      ということでソースを修正しました。

      先ず描画領域の無効化ですが列ヘッダーの領域のみを無効化していましたが、コントロール全体の領域を
      無効化するようにしました(505行目)

      次にBorderStyleが変更された時の列ヘッダーのセルの描画位置を調整しました(224~249行目、365~387行目)

      以上の修正でBorderStyle=NONE時の線が太くなる現象は無くなると思います。お試しください。

      また問題点ありましたらご指摘ください。

      削除
  3. このコメントは投稿者によって削除されました。

    返信削除
  4. はじめまして。
    こういう表示が行えるグリッドが欲しかったのです。

    こちらのものを参考にC#版を作成してみましたが、
    カラムの境界線をドラッグで移動すると、チラついてドラッグラインが見えなくなります。
    VBからC#への変更でこうなったのか定かではないのですが、VBの場合は大丈夫なのでしょうか。

    返信削除
    返信
    1. 自己回答です。
      以下のコードでドラッグ線が見えるようになるようです。
      とりあえずの対応です。。
      -------------------------------------------------------
      protected override void OnMouseDown(MouseEventArgs e)
      {
      base.OnMouseDown(e);
      this.DoubleBuffered = false;
      }

      protected override void OnMouseUp(MouseEventArgs e)
      {
      this.DoubleBuffered = true;
      base.OnMouseUp(e);
      }
      -------------------------------------------------------

      削除
    2. ご指摘ありがとうございます。

      VBでも境界線のドラッグラインは見なくなります。
      当コントロールに限らずDataGridViewにダブルバッファを適用するとドラッグラインは見えなくなる様です。
      実はちらつき防止を優先するあまり気にしていませんでした(汗)

      で自己回答で頂いたコードをVBでも試しました。みごとにドラッグライン見えました。
      オンマウス、マウスダウンのタイミングでダブルバッファ解除は思いつきませんでした。感謝感激です


      VBにも頂いたコードを盛り込んで近日中に再度アップしたいと思います。ありがとうございました。

      削除
    3. 境界線のドラッグラインの件、コード修正し最アップしました。

      削除
  5. はじめまして。

    こちらのコードをほぼ丸々使用させていただいているのですが、データ表示にヘッダーの項目をクリックしてもソートが行われません。
    またヘッダーに表示される▼ですが、データソースバインド前はヘッダーをクリックすると切り替わるのですが、データバインド後は▼が表示されません。
    visual studio 2013で動かしているのですが、何か心当たりはありませんでしょうか?

    返信削除
    返信
    1. 自己解決しました。

      ▼が表示されなくなる理由は不明ですが、ソートが出来ないのはデータソースにカスタムクラスのデータをバインドしていることが原因でした。
      お騒がせしました。

      削除
    2. コメントありがとうございます。

      自己解決されたとのこと何よりです。

      さて▼の表示についてですが、私の方ではVS2013の検証はできませんが今後引き続き調べたいと思います。

      削除
  6. お世話になります。
    列ヘッダー複数表示が通常のDataGridViewでは不可であることを知り、愕然となりつつ、いろいろググっても難しいソースがたくさんでてきて疲労困憊しておりましたところ、本HPにたどり着きました。

    丸っとソースを使用させて頂き(ソース公開して頂き、ありがとうございます)、四苦八苦しながらもソフト作成(VS2010)しております。

    さて、複数列ヘッダの文字列を取得したいのですがご教授願えないでしょうか。
    (例えば、ヘッダ3行目2列目の文字列を取得する)

    お手数をお掛けしますが、宜しくお願い致します。

    返信削除
    返信
    1. はじめまして。
      ご質問の件ですが、本コントロールの列ヘッダーコレクションの内容の取得により文字列を知ることができます。

      For i = 0 To Me.CustomHeaderDataGridView1.HeaderCells.Count - 1
      Console.WriteLine(Me.CustomHeaderDataGridView1.HeaderCells(i).Column & "," & _
      Me.CustomHeaderDataGridView1.HeaderCells(i).Row & ":" & _
      Me.CustomHeaderDataGridView1.HeaderCells(i).Text)
      Next

      ご質問の回答になってれば良いのですが…

      削除
    2. 早速のご回答の程、ありがとうございます。参考にさせて頂きます。

      重ねてではありますが、ヘッダ行番号とヘッダ列番号を指定して取得、ということはできないでしょうか。
      例えば、i:行番号、j:列番号 とした場合、
      DmyString = CustomHeaderDataGridView1.Rows(i).Cells(j).Value.ToString
      のように取得するイメージなのですが。

      お手数をお掛けしますが、宜しくお願い致します。

      削除
    3. 連投申し訳ありません。

      コレクションを空にするclear()ですが、データ表示している状態で、
      CustomHeaderDataGridView1.Rows.Clear()
      を実行すると、以下の例外発生します。
      MSG : System.ArgumentException: この一覧をクリアできません。
      場所 System.Data.DataView.System.Collections.IList.Clear()
      場所 System.Windows.Forms.DataGridViewRowCollection.Clear()

      本機能はサポート外ということでしょうか。(素人な質問ですみません)

      削除
    4. 二つの質問への回答です。

      一つ目の質問ですが、ご希望のイメージのような取得はできません。
      前回の返信のコードを参考に文字列を取得するロジックを考えてみては如何でしょうか。

      二つ目の質問ですが、ご示しののコードではDataGridView自体のRowのコレクションの初期化になります。
      ヘッダーコレクションの初期化をしたいのであるならば
      Me.CustomHeaderDataGridView1.HeaderCells.Clear()と記述すべきではないでしょうか。

      削除
  7. 早速のご回答の程、ありがとうございます。

    一つ目のご回答ですが、こちらでイメージしているような取得はできない旨、承知しました。
    取得ロジックを検討する前に、簡単に取得できれば、と思ったものです。ロジック検討します。

    二つ目のご回答ですが、内容参考させて頂きいろいろ試したところ、
    CustomHeaderDataGridView1.DataSource = ""
    CustomHeaderDataGridView1.HeaderCells.Clear()
    とすることで希望の動き(データを再表示する際に、一度消してから、再度表示させたい)となりました。

    ありがとうございました。

    返信削除
  8. お世話になっております。
    先日は回答の程、ありがとうございました。引き続き、本コントロールを活用させて頂いております。

    現在、ボタン列を追加することはできたのですが、ボタンの有効・無効の制御がなかなかうまくいきません。以下のサイトを参照しているのですが、『キャストできません』エラーとなってしまいます。
    (参照URL:https://msdn.microsoft.com/ja-jp/library/ms171619(v=vs.110).aspx)
    (方法 : Windows フォーム DataGridView コントロールのボタン列にあるボタンを無効にする)

    何かヒントや気づきなど、ございましたら教えて頂けないでしょうか。
    宜しくお願い致します。

    エラー内容:
    型 'System.Windows.Forms.DataGridViewButtonCell' のオブジェクトを型 'DataGridViewDisableButtonCell' にキャストできません。

    エラー発生箇所
    Dim buttonCell As DataGridViewDisableButtonCell = _
    DirectCast(CustomHeaderDataGridView1.Rows(行インデックス).Cells(列インデックス), _
    DataGridViewDisableButtonCell)

    事前に列は以下の方法で挿入しています:
    column = New DataGridViewDisableButtonColumn()
    CustomHeaderDataGridView1.Columns.Insert(列インデックス, column)

    返信削除
    返信
    1. すみません。
      自己解決しました。

      キャストする前にNew DataGridViewButtonCellとしている箇所がありました。
      お騒がせしました。

      削除
  9. はじめまして、ヘッダーの2行の実現でこちらに行き着きました。
    欲しい機能はほぼ実現できていると思うのですが、ヘッダー内で文字を折り返して表示することは可能でしょうか?
    ColumnのDefaultCellStyle.WrapModeをTrueにしても、列幅を超過した文字は「...」になって表示されます。

    ソースを少しだけ確認したのですが、仮に行うとするとテキストの描写を少し変更するだけではなく、1行目と2行目の描写を別々に行う必要がありそうですか?

    返信削除
    返信
    1. 自己解決しました。

      公開されているソースに以下の対応を行いました。
      ・HeaderCellクラスにWrapModeプロパティを追加
      ・GetTextFormatFlagsメソッドの引数にwrapModeを追加
      ・GetTextFormatFlagsメソッド内でwrapModeがTrueの場合に「formatFlg = formatFlg Or TextFormatFlags.WordBreak」を行う
      ・GetTextFormatFlagsを使用している箇所で必要なwrapModeを渡す

      長々となりますが一応ソースを貼り付けておきます。
      文字数制限に引っかかるのでコメントを分割します。

      削除
    2. コメントの制限上ヘッダコメントのタグキーワードは削除してありますので注意して下さい。

      CustomHeaderDataGridViewクラスのGetTextFormatFlagsメソッド
      ''' summary
      ''' 指定のスタイルから描写するテキストのスタイルを取得する
      ''' /summary
      ''' param name="alignment" テキストのスタイル /param
      ''' param name="wrapMode" 折り返し /param
      ''' returns 描写するテキストのスタイル /returns
      ''' remarks /remarks
      Private Function GetTextFormatFlags(ByVal alignment As DataGridViewContentAlignment, ByVal wrapMode As DataGridViewTriState) 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

      '折り返し
      Select Case wrapMode
      Case DataGridViewTriState.False
      Case DataGridViewTriState.NotSet
      Case DataGridViewTriState.True
      formatFlg = formatFlg Or TextFormatFlags.WordBreak
      End Select

      Return formatFlg

      Catch ex As Exception
      Throw
      End Try
      End Function

      CustomHeaderDataGridViewクラスのOnPaintメソッド
      見出しを最下列に表示の箇所
      Dim formatFlg As TextFormatFlags = GetTextFormatFlags(MyBase.ColumnHeadersDefaultCellStyle.Alignment, _
      MyBase.ColumnHeadersDefaultCellStyle.WrapMode)

      テキストの描写の箇所
      Dim formatFlg As TextFormatFlags = GetTextFormatFlags(Me.HeaderCells(i).TextAlign, Me.HeaderCells(i).WrapMode)
      Console.WriteLine(rect.ToString())
      TextRenderer.DrawText(e.Graphics, Me.HeaderCells(i).Text & sortText, MyBase.ColumnHeadersDefaultCellStyle.Font _
      , rect, foreColor, formatFlg)

      削除
    3. ソースの続きです。

      HeaderCellクラス
      Private _wrapMode As DataGridViewTriState = DataGridViewTriState.NotSet
      ''' summary
      ''' セルに含まれるテキスト形式の内容が 1 行に収まらないほど長い場合に、次の行に折り返されるか、切り捨てられるかを示す値を取得または設定する
      ''' /summary
      ''' value /value
      ''' returns /returns
      ''' remarks /remarks
      _

      Public Property WrapMode As DataGridViewTriState
      Get
      Return _wrapMode
      End Get
      Set(ByVal value As DataGridViewTriState)
      _wrapMode = value
      End Set
      End Property

      削除
    4. はじめまして、コメントありがとうございます。
      またコードのご教授ありがとうございます。
      恥ずかしながら文字折り返しについて全く注意が欠けていました。

      でコードの方を確認させて頂きました。
      HeaderCellクラスへWrapModeプロパティの追加、デフォルトのヘッダーセルの折り返しプロパティの反映までされていて素晴らしいです。

      つきましては当CustomDataGridviewにご教授頂いたコードを盛り込みたいと思いますが、ご許可を頂けるとありがたいです。

      何卒よろしくお願い致します。

      削除
    5. お返事ありがとうございます。
      自分のやった変更は微々たるものですし、コントロールのソースを公開して頂いていたからこそ変更を行うことができました。
      むしろ盛り込んで公開していただけると、こちらもすごく嬉しいです。

      削除
    6. ご快諾ありがとうございます。
      近日中にコードを盛り込んで更新したい思います。

      削除
    7. 遅くなりましたがソースを更新しました。ありがとうございました。

      削除
  10. はじめまして。
    こちらのクラスを是非利用させていただきたいのですが、C#版は公開されていないのでしょうか?

    返信削除
    返信
    1. 自己解決しました、すみません。
      VBのままプロジェクトをソリューションに追加することで、C#上で使用することができました。

      削除
    2. 返信遅くなりました。自己解決されたとのこと良かったです。

      さてC#版ですが、本ソースはC#に置き換えるのはさほど難易ではないと考えます。
      ただ時間ができたらC#版の公開も考えたいと思います。

      削除
  11. C#に変換したのですが、こちらに載せて構いませんか。

    返信削除
    返信
    1. C#変換お疲れ様です。ここへの掲載全然構いませんよ!

      削除
    2. ではお言葉に甘えて
      文字数制限のため、2つに分けて載せます。
      ・HeaderCell.cs
      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using System.Threading.Tasks;

      using System.Collections;
      using System.Data;
      using System.Diagnostics;
      using System.Windows.Forms;
      using System.ComponentModel;
      using System.Drawing;

      namespace MyLib
      {
      ///
      /// ヘッダーセル定義
      ///
      ///
      public class HeaderCell
      {

      private int _row;
      ///
      /// 行
      ///
      ///
      ///
      ///
      [Category("セル位置")]
      [Description("行")]
      public int Row
      {
      get { return _row; }
      set { _row = value; }
      }

      private int _column;
      ///
      /// 列
      ///
      ///
      ///
      ///
      [Category("セル位置")]
      [Description("列")]
      public int Column
      {
      get { return _column; }
      set { _column = value; }
      }

      private int _rowSpan = 1;
      ///
      /// 結合する行数
      ///
      ///
      ///
      ///
      [Category("セル結合")]
      [Description("行数")]
      public int RowSpan
      {
      get { return _rowSpan; }
      set { _rowSpan = value; }
      }

      private int _columnSpan = 1;
      ///
      /// 結合する列数
      ///
      ///
      ///
      ///
      [Category("セル結合")]
      [Description("列数")]
      public int ColumnSpan
      {
      get { return _columnSpan; }
      set { _columnSpan = value; }
      }

      private System.Drawing.Color _backgroundColor = Color.Empty;
      ///
      /// セルの背景色
      ///
      ///
      ///
      ///
      [Category("表示")]
      [Description("セルの背景色")]
      public System.Drawing.Color BackgroundColor
      {
      get { return _backgroundColor; }
      set { _backgroundColor = value; }
      }

      private System.Drawing.Color _foreColor = Color.Empty;
      ///
      /// テキストの文字色
      ///
      ///
      ///
      ///
      [Category("表示")]
      [Description("テキストの文字色")]
      public System.Drawing.Color ForeColor
      {
      get { return _foreColor; }
      set { _foreColor = value; }
      }


      private string _text;
      ///
      /// セルに関連付けられたテキスト
      ///
      ///
      ///
      ///
      [Category("表示")]
      [Description("セルに関連付けられたテキストです")]
      public string Text
      {
      get { return _text; }
      set { _text = value; }
      }

      private DataGridViewContentAlignment _textAlign;
      ///
      /// 結合されたセル内でのテキストの位置
      ///
      ///
      ///
      ///
      [Category("表示")]
      [Description("結合されたセル内のテキストの位置を決定します")]
      public DataGridViewContentAlignment TextAlign
      {
      get { return _textAlign; }
      set { _textAlign = value; }
      }

      private DataGridViewTriState _wrapMode = DataGridViewTriState.NotSet;
      ///
      /// セルに含まれるテキスト形式の内容が 1 行に収まらないほど長い場合に、次の行に折り返されるか、
      /// 切り捨てられるかを示す値を取得または設定する
      ///
      ///
      ///
      ///
      [Category("表示")]
      [Description("セル内のテキストが一行に収まらない場合にテキストを折り返す")]
      public DataGridViewTriState WrapMode
      {
      get { return _wrapMode; }
      set { _wrapMode = value; }
      }

      private bool _sortVisible;
      ///
      /// 結合されている列に並び替えがある場合に並び替えの方向を表示する
      ///
      ///
      ///
      ///
      [Category("表示")]
      [Description("結合されている列に並び替えがある場合に並び替えの方向を表示する")]
      public bool SortVisible
      {
      get { return _sortVisible; }
      set { _sortVisible = value; }
      }
      }
      }

      削除
    3. 2つに分けても長過ぎました。CustomHeaderDataGridViewも3つに分割します。
      ・CustomHeaderDataGridView.cs
      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using System.Threading.Tasks;
      using System.Collections;
      using System.Data;
      using System.Diagnostics;
      using System.Windows.Forms;
      using System.ComponentModel;
      using System.Drawing;

      namespace MyLib
      {
      public class CustomHeaderDataGridView : System.Windows.Forms.DataGridView
      {

      private MyCollection _item;

      ///
      /// 列ヘッダに表示するCellを設定します
      ///
      ///
      ///
      ///
      [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
      [Category("列ヘッダのカスタマイズ")]
      [Description("列ヘッダに表示するCellを設定します")]
      public MyCollection HeaderCells
      {
      get { return _item; }
      }

      internal void OnCollectionChanged()
      {
      this.Invalidate();
      }



      ///
      /// コレクションの設定
      ///
      ///
      public class MyCollection : System.Collections.ObjectModel.Collection
      {

      private CustomHeaderDataGridView _parent;

      internal MyCollection(CustomHeaderDataGridView parent)
      {
      _parent = parent;
      }

      protected override void ClearItems()
      {
      base.ClearItems();
      _parent.OnCollectionChanged();
      }

      protected override void InsertItem(int index, HeaderCell item)
      {
      base.InsertItem(index, item);
      _parent.OnCollectionChanged();
      }

      protected override void RemoveItem(int index)
      {
      base.RemoveItem(index);
      _parent.OnCollectionChanged();
      }

      protected override void SetItem(int index, HeaderCell item)
      {
      base.SetItem(index, item);
      _parent.OnCollectionChanged();
      }

      }


      private int _columnHeaderRowCount = 1;
      ///
      /// 列ヘッダーの行数を設定します
      ///
      ///
      ///
      ///
      [Category("列ヘッダのカスタマイズ")]
      [Description("列ヘッダに表示する行を設定します")]
      public int ColumnHeaderRowCount
      {
      get { return _columnHeaderRowCount; }
      set
      {



      _columnHeaderRowCount = value;

      if (value == 0)
      {
      _columnHeaderRowCount = 1;
      }

      base.ColumnHeadersHeight = value * ColumnHeaderRowHeight + 2;
      base.Refresh();
      }
      }

      private int _columnHeaderRowHeight = 17;
      ///
      /// 列ヘッダに表示する行の高さ
      ///
      ///
      ///
      ///
      [Category("列ヘッダのカスタマイズ")]
      [Description("列ヘッダに表示する行の高さを設定します")]
      public int ColumnHeaderRowHeight
      {
      get { return _columnHeaderRowHeight; }
      set
      {
      _columnHeaderRowHeight = value;

      base.ColumnHeadersHeight = value * ColumnHeaderRowCount + 2;
      base.Refresh();
      }
      }

      ///
      /// 列ヘッダーの境界線の種類
      ///
      ///
      public enum HeaderCellBorderStyle
      {
      SingleLine = 0,
      DoubleLine = 1
      }

      private HeaderCellBorderStyle _columnHeaderBorderStyle = HeaderCellBorderStyle.SingleLine;
      ///
      /// 列ヘッダーの線種
      ///
      ///
      ///
      ///
      [Category("列ヘッダのカスタマイズ")]
      [Description("列ヘッダに線種を設定します")]
      public HeaderCellBorderStyle ColumnHeaderBorderStyle
      {
      get { return _columnHeaderBorderStyle; }
      set
      {
      _columnHeaderBorderStyle = value;
      base.Refresh();
      }
      }

      削除
    4. ・CustomHeaderDataGridView.cs(2)
      [System.Diagnostics.DebuggerNonUserCode()]
      public CustomHeaderDataGridView() : base()
      {
      //この呼び出しは、コンポーネント デザイナーで必要です。
      InitializeComponent();

      this._item = new MyCollection(this);
      base.DoubleBuffered = true;

      }

      //Component は、コンポーネント一覧に後処理を実行するために dispose をオーバーライドします。
      [System.Diagnostics.DebuggerNonUserCode()]
      protected override void Dispose(bool disposing)
      {
      try
      {
      if (disposing && components != null)
      {
      components.Dispose();
      }
      }
      finally
      {
      base.Dispose(disposing);
      }
      }

      //コンポーネント デザイナーで必要です。
      private System.ComponentModel.IContainer components;

      //メモ: 以下のプロシージャはコンポーネント デザイナーで必要です。
      //コンポーネント デザイナーを使って変更できます。
      //コード エディターを使って変更しないでください。
      [System.Diagnostics.DebuggerStepThrough()]
      private void InitializeComponent()
      {
      components = new System.ComponentModel.Container();
      }



      削除
    5. 3分割で終わらなかった…
      ・CustomHeaderDataGridView.cs(3)
      /// 再描画をするとき
      protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
      {
      base.OnPaint(e);
      try
      {


      //---------------------------------------------------------------------------------------------------------
      //ヘッダーセルの描画
      //---------------------------------------------------------------------------------------------------------

      //ヘッダーの行の高さの取得
      int rowHeight = base.ColumnHeadersHeight;

      if (this.ColumnHeaderRowCount > 0)
      {
      rowHeight = base.ColumnHeadersHeight / this.ColumnHeaderRowCount;
      }

      //線の太さ
      int lineWidth = 1;

      for (int i = 0; i <= ColumnCount - 1; i++)
      {

      for (int j = 0; j <= this.ColumnHeaderRowCount - 1; j++)
      {

      //グッリドの線
      Pen gridPen = new Pen(base.GridColor);

      //背景色
      SolidBrush backBrash = new SolidBrush(base.ColumnHeadersDefaultCellStyle.BackColor);

      //くぼみ線
      SolidBrush whiteBrash = new SolidBrush(Color.White);

      try
      {
      //列ヘッダーの描画領域
      Rectangle rect = base.GetCellDisplayRectangle(i, -1, true);

      //列ヘッダーの描画領域の底部の座標を保存
      int btm = rect.Bottom;

      //セルの描画領域のY座標
      switch (base.BorderStyle)
      {
      case BorderStyle.None:
      rect.Y = rowHeight * j;
      break;
      case BorderStyle.FixedSingle:
      rect.Y = rowHeight * j + lineWidth;
      break;
      case BorderStyle.Fixed3D:
      rect.Y = rowHeight * j + (lineWidth * 2);
      break;
      }

      //セルの描画領域のX座標
      rect.X -= lineWidth;

      //セルの描画領域の高さ
      rect.Height = rowHeight;

      //最下行の場合高さを調整
      if (j == this.ColumnHeaderRowCount - 1)
      {
      rect.Height = btm - rect.Y - lineWidth;
      }

      //セルを囲む線の描画
      e.Graphics.DrawRectangle(gridPen, rect);


      //セルの背景色の領域
      rect.Y += lineWidth;
      rect.X += lineWidth;
      rect.Height -= lineWidth;
      rect.Width -= lineWidth;

      //背景色の描画
      if (ColumnHeaderBorderStyle != HeaderCellBorderStyle.DoubleLine)
      {
      //Single線の場合
      e.Graphics.FillRectangle(backBrash, rect);
      }
      else
      {
      //くぼみ線の場合
      //rect.Width -= lineWidth
      e.Graphics.FillRectangle(whiteBrash, rect);
      rect.Y += lineWidth;
      rect.X += lineWidth;
      rect.Height -= lineWidth;
      rect.Width -= lineWidth;

      e.Graphics.FillRectangle(backBrash, rect);
      }

      //見出しを最下列に表示
      if (j == this.ColumnHeaderRowCount - 1)
      {
      string text = base.Columns[i].HeaderText;

      if (base.SortedColumn != null && object.ReferenceEquals(base.SortedColumn, this.Columns[i]))
      {
      if (base.SortOrder == SortOrder.Ascending)
      {
      text = text + " ▼";
      }
      else if (base.SortOrder == SortOrder.Descending)
      {
      text = text + " ▲";
      }
      }

      TextFormatFlags formatFlg = GetTextFormatFlags(base.ColumnHeadersDefaultCellStyle.Alignment, base.ColumnHeadersDefaultCellStyle.WrapMode);

      TextRenderer.DrawText(e.Graphics, text, base.ColumnHeadersDefaultCellStyle.Font, rect, base.ColumnHeadersDefaultCellStyle.ForeColor, formatFlg);
      }

      }
      finally
      {
      //リソースの解放
      gridPen.Dispose();
      backBrash.Dispose();
      whiteBrash.Dispose();
      }
      }
      }


      削除
    6. ・CustomHeaderDataGridView.cs(4)
      //---------------------------------------------------------------------------------------------------------
      //ヘッダーのセル結合
      //---------------------------------------------------------------------------------------------------------
      //ヘッダーセル定義の処理
      for (int i = 0; i <= this.HeaderCells.Count - 1; i++)
      {

      //セルの結合の開始行がヘッダーの行数より大きい場合は除外
      if (HeaderCells[i].Row < this.ColumnHeaderRowCount - 1)
      {
      continue;
      }

      //セルの結合の開始列の列インデックスが列数より大きい場合は除外
      if (HeaderCells[i].Column < base.ColumnCount - 1)
      {
      continue;
      }

      //描画領域の設定
      Rectangle rect = Rectangle.Empty;

      //結合する列中のソート状態
      string sortText = string.Empty;

      //結合するセルの各列の幅を取得し描画領域の幅を決める、ソートされている列の場合Textに表示するソート方向の設定
      for (int j = this.HeaderCells[i].Column; j <= this.HeaderCells[i].Column + this.HeaderCells[i].ColumnSpan - 1; j++)
      {

      //列が画面に表示されていない場合は処理しない
      if (base.Columns[j].Displayed == false)
      {
      continue;
      }

      //列ヘッダーの領域の幅
      if (rect.IsEmpty)
      {
      //結合するセルの開始列の場合
      rect = base.GetCellDisplayRectangle(j, -1, true);
      }
      else
      {
      //結合するセルの2列目以降の場合
      rect.Width += base.GetCellDisplayRectangle(j, -1, true).Width;
      }


      //ソート列の場合
      if (HeaderCells[i].SortVisible == true && base.SortedColumn != null && object.ReferenceEquals(base.SortedColumn, base.Columns[j]))
      {
      if (base.SortOrder == SortOrder.Ascending)
      {
      sortText = " ▼";
      }
      else if (base.SortOrder == SortOrder.Descending)
      {
      sortText = " ▲";
      }
      }

      }

      //結合するセルが画面中に無い場合
      if (rect == null)
      {
      continue;
      }

      //結合する行がヘッダー行数より大きい場合
      int rowSapn = this.HeaderCells[i].RowSpan;
      if (rowSapn < ColumnHeaderRowCount)
      {
      rowSapn = ColumnHeaderRowCount;
      }

      //列ヘッダーの描画領域の底部の座標を保存
      int btm = rect.Bottom;

      削除
    7. ・CustomHeaderDataGridView.cs(5)
      //結合するセルの描画領域のY座標
      switch (base.BorderStyle)
      {
      case BorderStyle.None:
      rect.Y = rowHeight * (this.HeaderCells[i].Row);
      break;
      case BorderStyle.FixedSingle:
      rect.Y = rowHeight * (this.HeaderCells[i].Row) + lineWidth;
      break;
      case BorderStyle.Fixed3D:
      rect.Y = rowHeight * (this.HeaderCells[i].Row) + (lineWidth * 2);
      break;
      }

      //結合するセルの描画領域のX座標
      rect.X -= lineWidth;

      //結合するセルの描画領域の高さ
      rect.Height = rowHeight * rowSapn;

      //最下行の場合は描画領域の高さを調整する
      if (this.HeaderCells[i].Row + rowSapn == this.ColumnHeaderRowCount)
      {
      rect.Height = btm - rect.Y - lineWidth;
      }

      //グッリドの線
      Pen gridPen = new Pen(base.GridColor);

      //背景色の取得
      System.Drawing.Color backgroundColor = base.ColumnHeadersDefaultCellStyle.BackColor;
      //セルの背景色が設定されている場合
      if (!(this.HeaderCells[i].BackgroundColor == Color.Empty))
      {
      backgroundColor = this.HeaderCells[i].BackgroundColor;
      }

      //背景色
      SolidBrush backBrash = new SolidBrush(backgroundColor);

      //くぼみ線
      SolidBrush whiteBrash = new SolidBrush(Color.White);

      try
      {

      //枠線の描画
      e.Graphics.DrawRectangle(gridPen, rect);


      //結合セルの背景色の描画領域の設定
      rect.Y += lineWidth;
      rect.X += lineWidth;
      rect.Height -= lineWidth;
      rect.Width -= lineWidth;


      //背景色の描画
      if (ColumnHeaderBorderStyle == HeaderCellBorderStyle.SingleLine)
      {
      //Singleの場合
      e.Graphics.FillRectangle(backBrash, rect);
      }
      else
      {
      //くぼみ線の場合
      e.Graphics.FillRectangle(whiteBrash, rect);
      rect.Y += lineWidth;
      rect.X += lineWidth;
      rect.Height -= lineWidth;
      rect.Width -= lineWidth;

      e.Graphics.FillRectangle(backBrash, rect);
      }


      //テキストの描画
      System.Drawing.Color foreColor = base.ColumnHeadersDefaultCellStyle.ForeColor;
      if (!(this.HeaderCells[i].ForeColor == Color.Empty))
      {
      foreColor = this.HeaderCells[i].ForeColor;
      }

      TextFormatFlags formatFlg = GetTextFormatFlags(this.HeaderCells[i].TextAlign, this.HeaderCells[i].WrapMode);

      TextRenderer.DrawText(e.Graphics, this.HeaderCells[i].Text + sortText, base.ColumnHeadersDefaultCellStyle.Font, rect, foreColor, formatFlg);

      }
      finally
      {
      //リソースの解放
      gridPen.Dispose();
      backBrash.Dispose();
      whiteBrash.Dispose();
      }
      }

      }
      catch (Exception ex)
      {
      MessageBox.Show(ex.ToString());
      }
      }

      削除
    8. ・CustomHeaderDataGridView.cs(6)
      /// <summary>
      /// 指定のスタイルから描写するテキストのスタイルを取得する
      /// </summary>
      /// <param name="alignment">テキストのスタイル</param>
      /// <param name="wrapMode">折り返</param>
      /// <remarks>描写するテキストのスタイル</remarks>
      private TextFormatFlags GetTextFormatFlags(DataGridViewContentAlignment alignment, DataGridViewTriState wrapMode)
      {
      try
      {
      //'文字の描画
      TextFormatFlags formatFlg = TextFormatFlags.Right | TextFormatFlags.VerticalCenter | TextFormatFlags.EndEllipsis;

      //表示位置
      switch (alignment)
      {
      case DataGridViewContentAlignment.BottomCenter:
      formatFlg = TextFormatFlags.Bottom | TextFormatFlags.HorizontalCenter | TextFormatFlags.EndEllipsis;
      break;
      case DataGridViewContentAlignment.BottomLeft:
      formatFlg = TextFormatFlags.Bottom | TextFormatFlags.Left | TextFormatFlags.EndEllipsis;
      break;
      case DataGridViewContentAlignment.BottomRight:
      formatFlg = TextFormatFlags.Bottom | TextFormatFlags.Right | TextFormatFlags.EndEllipsis;
      break;
      case DataGridViewContentAlignment.MiddleCenter:
      formatFlg = TextFormatFlags.VerticalCenter | TextFormatFlags.HorizontalCenter | TextFormatFlags.EndEllipsis;
      break;
      case DataGridViewContentAlignment.MiddleLeft:
      formatFlg = TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis;
      break;
      case DataGridViewContentAlignment.MiddleRight:
      formatFlg = TextFormatFlags.VerticalCenter | TextFormatFlags.Right | TextFormatFlags.EndEllipsis;
      break;
      case DataGridViewContentAlignment.TopCenter:
      formatFlg = TextFormatFlags.Top | TextFormatFlags.HorizontalCenter | TextFormatFlags.EndEllipsis;
      break;
      case DataGridViewContentAlignment.TopLeft:
      formatFlg = TextFormatFlags.Top | TextFormatFlags.Left | TextFormatFlags.EndEllipsis;
      break;
      case DataGridViewContentAlignment.TopRight:
      formatFlg = TextFormatFlags.Top | TextFormatFlags.Right | TextFormatFlags.EndEllipsis;
      break;
      }


      //折り返し
      switch (wrapMode)
      {
      case DataGridViewTriState.False:
      break;
      case DataGridViewTriState.NotSet:
      break;
      case DataGridViewTriState.True:
      formatFlg = formatFlg | TextFormatFlags.WordBreak;
      break;
      }

      return formatFlg;

      }
      catch (Exception ex)
      {
      throw ex;
      }
      }

      /// <summary>
      /// セルを結合する対象の列の描画領域の無効化
      /// </summary>
      /// <remarks></remarks>
      private void InvalidateUnitColumns()
      {
      try
      {

      Rectangle hRect = base.DisplayRectangle;
      hRect.Height = base.ColumnHeadersHeight + 1;
      base.Invalidate(hRect);

      }
      catch (Exception ex)
      {
      MessageBox.Show(ex.ToString());
      }
      }

      削除
    9. dragonさん、差し支えなければ後で一つにまとめて本文へ掲載し直しますがいかがでしょうか?

      削除
    10. そうですね。ファイル添付できればよかったのですが。お手数ですがお願いします。これで終わりです。
      ・CustomHeaderDataGridView.cs(7)
      /// <summary>
      /// スクロールが実行されたとき
      /// </summary>
      /// <param name="e"></param>
      /// <remarks></remarks>
      protected override void OnScroll(System.Windows.Forms.ScrollEventArgs e)
      {
      base.OnScroll(e);

      try
      {
      InvalidateUnitColumns();
      }
      catch (Exception ex)
      {
      MessageBox.Show(ex.ToString());
      }
      }

      /// <summary>
      /// サイズが変更されたとき
      /// </summary>
      /// <param name="e"></param>
      /// <remarks></remarks>
      protected override void OnSizeChanged(System.EventArgs e)
      {
      base.OnSizeChanged(e);

      try
      {
      InvalidateUnitColumns();
      }
      catch (Exception ex)
      {
      MessageBox.Show(ex.ToString());
      }

      }

      /// <summary>
      /// 列の幅が変更されたとき
      /// </summary>
      /// <param name="e"></param>
      /// <remarks></remarks>
      protected override void OnColumnWidthChanged(System.Windows.Forms.DataGridViewColumnEventArgs e)
      {
      base.OnColumnWidthChanged(e);

      try
      {
      InvalidateUnitColumns();
      }
      catch (Exception ex)
      {
      MessageBox.Show(ex.ToString());
      }
      }

      /// <summary>
      /// 行の境界線がダブルクリックされた時
      /// </summary>
      /// <param name="e"></param>
      /// <remarks></remarks>
      protected override void OnRowDividerDoubleClick(System.Windows.Forms.DataGridViewRowDividerDoubleClickEventArgs e)
      {
      base.OnRowDividerDoubleClick(e);

      try
      {
      //行ヘッダーの境界線がダブルクリックされたへっだーの高さを整える
      if (e.RowIndex == -1)
      {
      base.ColumnHeadersHeight = this.ColumnHeaderRowCount * this.ColumnHeaderRowHeight + 2;
      }
      }
      catch (Exception ex)
      {
      MessageBox.Show(ex.ToString());
      }
      }

      /// <summary>
      /// マウスのボタンが押された時
      /// </summary>
      /// <param name="e"></param>
      /// <remarks></remarks>
      protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
      {
      base.OnMouseDown(e);

      try
      {
      //列幅、行高を調整するドラグ線を見えるようにするためにダブルバッファを解除する
      base.DoubleBuffered = false;
      }
      catch (Exception ex)
      {
      MessageBox.Show(ex.ToString());
      }
      }

      /// <summary>
      /// マウスのボタンが離された時
      /// </summary>
      /// <param name="e"></param>
      /// <remarks></remarks>
      protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e)
      {
      base.OnMouseUp(e);

      try
      {
      //OnMouseDownイベントで解除されたダブルバッファを適用する
      base.DoubleBuffered = true;
      }
      catch (Exception ex)
      {
      MessageBox.Show(ex.ToString());
      }
      }
      }
      }

      削除
    11. dragonさん、C#コードの掲載ありがとうごさいます。
      遅くなりましたが「DataGridViewの列ヘッダーの複数行表示するカスタムコントロール①」へコードを取りまとめて掲載しました。
      尚、三ヶ所ほどif文の判定が逆になっていたようなのでこちらで直しましたのでご了承ください。

      削除
  12. はじめまして
    コードを利用させていただいてます。

    バグなのかもしれませんが
    デザインにてCustomDataGridView(以下CDGV)コントロールを貼り付けた段階ではコントロール枠が見えていますが
    開始(プログラムの実行)をしたあとに再度デザインを見るとCDGVの枠が見えなくなってしまいます。
    一度デザインを閉じてから開くと枠は見えます。

    これが原因で他コントロールのAnchorプロパティが機能せず、フォーム最大化時にCDGVと重なってしまいます。

    すみませんが直し方を教えて下さい。

    返信削除
    返信
    1. 環境はVisual Studio 2019 .NET Framework 4.7.2 です。

      削除
  13. 1点教えてください。
    ヘッダー行を複数としたときに、各ヘッダーの高さを変更することは出来ますか。

    返信削除
  14. はじめまして。
    掲載されているVBのコードを流用させていただいております。
    当方の開発環境はVS2022です。
    カスタムコントロール①に掲載されたコードを設定したあと、カスタムコントロール②に掲載された使用例の設定をHeaderCellコレクションエディターの個所まで行いましたが、カスタム化したDataGridViewには設定したタイトルが表示されません。
    何かアドバイス等ありましたら、お願いできないでしょうか。

    どうぞよろしくお願いします。

    返信削除