- PR -

DataGridViewのヘッダーにチェックボックス

投稿者投稿内容
Haru
常連さん
会議室デビュー日: 2009/02/18
投稿数: 37
投稿日時: 2009-02-27 14:08
引用:

くまっちさんの書き込み (2009-02-27 12:29) より:
2.3の項を見て、考えているイメージが違うと思いましたので、ご指摘致します。
チェックボックスコントロールと同じ機能を実装するのであって
チェックボックスコントロールの追加ではありません。



やり方はわかっていないものの、コントロールの追加で考えていました。
コントロールと同じ機能を実装ということは、イベントの処理なども個別に実装しないといけないのでしょうか?

引用:

1.については、OverrideしているPaintにチェックボックスを描画するコード記述
System.Windows.Forms.CheckBoxRendererのDrawCheckBoxを使用すると楽です。



Paint内に次のコードを記述しました。

コード:
        ' Use the base method to paint the default appearance. 
        MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, _
            cellState, value, formattedValue, errorText, cellStyle, _
            advancedBorderStyle, paintParts)

        Dim headerStr As String = "文字列"
        Dim cFont As Font = SystemFonts.DefaultFont
        chkBox.Text = headerStr
        chkBox.Checked = True 'System.Windows.Forms.VisualStyles.CheckBoxState.CheckedNormal

        Dim locX As Integer = cellBounds.X + (cellBounds.Width / 2)
        Dim locY As Integer = cellBounds.Y + (cellBounds.Height - 13) / 2
        Dim checkBoxLocation As New Point(locX, locY)

        Dim recX As Integer = cellBounds.X + 16
        Dim recY As Integer = cellBounds.Y
        Dim recW As Integer = cellBounds.Width - 16
        Dim recH As Integer = cellBounds.Height

        Dim checkBoxTextRect As New Rectangle(recX, recY, recW, recH)
        'テキストの表示法
        Dim checkBoxTextFormat As TextFormatFlags = _
            TextFormatFlags.HorizontalCenter Or TextFormatFlags.VerticalCenter
        'チェックされた普通の状態とする
        CheckBoxRenderer.DrawCheckBox(graphics, checkBoxLocation, checkBoxTextRect, _
            chkBox.Text, chkBox.Font, checkBoxTextFormat, False, _
            chkBox.Checked)



また、上記の他に
Dim chkBox As CheckBox
としてチェックボックス用の変数を用意し、New()の中で
chkBox = New CheckBox
でインスタンスを作成しています。

以上のコードを記述することによりチェックボックスは表示できましたが
4.headerStrにヘッダーの文字列を入れる方法を教えて下さい
5.cFontにヘッダーのフォントを入れる方法を教えて下さい
6.ヘッダーに元々設定されている文字列と、headerStrの両方が表示されてしまうのでヘッダーに元々設定されている文字列を表示しないようにする方法を教えて下さい
7、チェックボックスの後ろにheaderStrを表示する方法を教えて下さい。

※結局、チェックボックスを表示する方法を教えて頂く必要がありそうです。
どうも上記のコードはチェックボックスとその文字列がバラバラに表示されているだけで、お互いに関連が無いようです。
チェックボックスの変数(chkBox)は用意したものの、チェックボックスとしては使えていません。
本来はCheckBoxRenderer.DrawCheckBoxでchkBoxを表示するのではないかとぼんやり考えていますがやり方が分からず、ここで止まってしまいました。

引用:

2.3.については、OnClick等をオーバーライドしてチェックボックスと同じ動き実装
って事になります。


コントロールと同じ機能を実装と絡むと思いますが、オーバーライドの仕方を具体的に教えて下さい。



くまっち
大ベテラン
会議室デビュー日: 2008/01/18
投稿数: 169
お住まい・勤務地: 茨城県のどこか。
投稿日時: 2009-02-27 15:38
サンプル作ってみましたので参考にしてください。
下記コードのみでとりあえずなチェックボックスを持つヘッダが実現出来ます。

コード:
Imports System.Windows.Forms
Imports System.ComponentModel

'' カスタム列ヘッダセル
Public Class DataGridViewCustomCheckBoxHeaderCell
    Inherits DataGridViewColumnHeaderCell

    Private _checkState As Boolean

    Protected Overrides Sub Paint( _
        ByVal graphics As Graphics, _
        ByVal clipBounds As Rectangle, _
        ByVal cellBounds As Rectangle, _
        ByVal rowIndex As Integer, _
        ByVal cellState As DataGridViewElementStates, _
        ByVal value As Object, _
        ByVal formattedValue As Object, _
        ByVal errorText As String, _
        ByVal cellStyle As DataGridViewCellStyle, _
        ByVal advancedBorderStyle As DataGridViewAdvancedBorderStyle, _
        ByVal paintParts As DataGridViewPaintParts)

        '' 既存のヘッダーは背景や枠だけ描画すればいいので、テキストは空指定
        MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, _
            cellState, String.Empty, String.Empty, String.Empty, cellStyle, _
             advancedBorderStyle, paintParts)

        '' 文字描画の必要領域を求める
        Dim drawTextSize As SizeF
        drawTextSize = graphics.MeasureString(formattedValue, cellStyle.Font)

        '' チェックボックス本体の描画位置を求める
        Dim location As New Point
        location.X = cellBounds.X + (cellBounds.Width - drawTextSize.Width) / 2 - 14
        location.Y = cellBounds.Y + (cellBounds.Height - 12) / 2

        '' チェック状態からCheckBoxStateを求める
        Dim checkBoxState As VisualStyles.CheckBoxState
        checkBoxState = IIf(Me._checkState, _
            VisualStyles.CheckBoxState.CheckedNormal, VisualStyles.CheckBoxState.UncheckedNormal)

        '' チェックボックスを描画
        System.Windows.Forms.CheckBoxRenderer.DrawCheckBox( _
            graphics, location, cellBounds, formattedValue, cellStyle.Font, False, checkBoxState)
    End Sub

    Protected Overrides Sub OnClick(ByVal e As System.Windows.Forms.DataGridViewCellEventArgs)
        _checkState = _checkState Xor True
        MyBase.OnClick(e)
        Me.RaiseCheckBoxCheckedChanged(EventArgs.Empty)
    End Sub
    Protected Overrides Sub OnDoubleClick(ByVal e As System.Windows.Forms.DataGridViewCellEventArgs)
        _checkState = _checkState Xor True
        MyBase.OnDoubleClick(e)
        Me.RaiseCheckBoxCheckedChanged(EventArgs.Empty)
    End Sub

    Private Sub RaiseCheckBoxCheckedChanged(ByVal e As EventArgs)
        '' チェックボックスの状態(On/Off)が変化した時の処理は以下に記述

        '' サンプル
        MessageBox.Show(String.Format("チェックボックスが[{0}]になりました。", _
            IIf(Me._checkState, "On", "Off")), "", MessageBoxButtons.OK)
    End Sub

End Class

'' カスタム列ヘッダクラスを使用するカスタム列クラス
Public Class DataGridViewCustomCheckBoxHeaderColumn
    Inherits DataGridViewTextBoxColumn

    Public Sub New()
        MyBase.DefaultHeaderCellType = GetType(DataGridViewCustomCheckBoxHeaderCell)

        '' ソート方向を示すグリフ表示をさせないためにソート不可(コード上からのソートのみ)に設定
        Me.SortMode = DataGridViewColumnSortMode.NotSortable
    End Sub

    <EditorBrowsable(EditorBrowsableState.Never), Browsable(False), _
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
    Public Shadows ReadOnly Property DefaultHeaderCellType() As Type
        Get
            Return GetType(DataGridViewCustomCheckBoxHeaderCell)
        End Get
    End Property

End Class



上記は、単なるチェックボックスがあるヘッダを表示するための最低限必要だと思われるコードです。
したがって、汎用性はありません。(※3ステートチェック未対応だったり、表示位置固定だったり・・・)

オーバーライドは[Overrides]修飾子を付けて元関数と同じように宣言することです。
(オーバーライド出来る関数は[Overridable or MustOverride]修飾子がある関数です)
当サンプルでは、Paint,OnClick,OnDoubleClickが該当します。
Haru
常連さん
会議室デビュー日: 2009/02/18
投稿数: 37
投稿日時: 2009-02-27 17:37
引用:

くまっちさんの書き込み (2009-02-27 15:38) より:
サンプル作ってみましたので参考にしてください。
下記コードのみでとりあえずなチェックボックスを持つヘッダが実現出来ます。



お手数をお掛けし申し訳ありません。
大変参考になります。
ありがとうございました。

早速利用させて頂きました。

私はデータ行もチェックボックスで使いたかったので

コード:

Public Class DataGridViewCustomCheckBoxHeaderColumn
Inherits DataGridViewTextBoxColumn



コード:

Public Class DataGridViewCustomCheckBoxHeaderColumn
Inherits DataGridViewCheckBoxColumn


に変更させて頂いたところほぼ期待通りの動きが実現できました。


引用:

上記は、単なるチェックボックスがあるヘッダを表示するための最低限必要だと思われるコードです。
したがって、汎用性はありません。(※3ステートチェック未対応だったり、表示位置固定だったり・・・)


3ステートチェックもサポートしたいのですが、こちらについてはもう少し調べてみたいと思います。(この後の質問への回答が大きなヒントになることを期待して・・・)
表示位置固定というのはヘッダーの文字列の位置のことだと思いますが、チェックボックスと文字列の間を若干広げたいのでヒントだけでも教えて頂けないでしょうか。
それと、チェックボックスのON/OFFが画面に反映されないのですが修正方法を教えて頂けないでしょうか。
OnClickの中で
コード:

Me.Selected = Me._checkState
MyClass.Selected = Me._checkState
MyBase.Selected = Me._checkState


の3パターンを試したのですが実行時に「Selected を変更できない」と怒られてしまいました。
試しにMyBase.Value = _checkStateと書いたら、チェックボックスの状態は書き換わるようになりましたが、ヘッダーの文字列も一緒にTrue/Falseと書き換わるようになりました。
チェックボックスの状態を_checkStateと同じ状態にするためにはどのプロパティにアクセスすれば良いのでしょうか?
また、クラス内部からヘッダーの文字列にアクセスするにはどうすれば良いのでしょうか
※フォーム側だと「Me.Column1.HeaderText = "CHK"」のようにしてアクセスできることは確認しました。

引用:

オーバーライドは[Overrides]修飾子を付けて元関数と同じように宣言することです。
(オーバーライド出来る関数は[Overridable or MustOverride]修飾子がある関数です)
当サンプルでは、Paint,OnClick,OnDoubleClickが該当します。



ありがとうございます。
ということは、フォーム側では通常の使い方と同じだと思い確認のため
コード:

Private Sub DataGridView1_CellClick(ByVal sender As System.Object,
ByVal e As System.Windows.Forms.DataGridViewCellEventArgs)
Handles DataGridView1.CellClick
If DataGridView1.Columns(e.ColumnIndex).Name = "Column1" Then
MessageBox.Show(String.Format("チェックボックスが[{0}]になりました。", _
IIf(Me.Column1.Selected, "On", "Off")),
"DataGridView1_CellClick", MessageBoxButtons.OK)
End If

End Sub

Private Sub DataGridView1_ColumnHeaderMouseClick(ByVal sender As
System.Object, ByVal e As System.Windows.Forms.DataGridViewCellMouseEventArgs)
Handles DataGridView1.ColumnHeaderMouseClick
If DataGridView1.Columns(e.ColumnIndex).Name = "Column1" Then
MessageBox.Show(String.Format("チェックボックスが[{0}]になりました。", _
IIf(Me.Column1.Selected, "On", "Off")),
"DataGridView1_ColumnHeaderMouseClick", MessageBoxButtons.OK)
End If

End Sub


と書いてみましたが、どちらのイベントも実行されてしまいました。
DataGridView1_CellClick内でヘッダがクリックされたか否かを判定する方法と、イベント内でヘッダーのチェックボックスの内容を確認する方法を教えて下さい。


[ メッセージ編集済み 編集者: Haru 編集日時 2009-02-27 17:46 ]
Haru
常連さん
会議室デビュー日: 2009/02/18
投稿数: 37
投稿日時: 2009-02-27 18:21
引用:

DataGridView1_CellClick内でヘッダがクリックされたか否かを判定する方法と、




以下のコードを書いてみましたがヘッダかどうかの判定はe.RowIndex >= 0で合ってますでしょうか?
コード:

Private Sub DataGridView1_CellClick(ByVal sender As System.Object,
ByVal e As System.Windows.Forms.DataGridViewCellEventArgs)
Handles DataGridView1.CellClick
If e.RowIndex >= 0 Then
If DataGridView1.Columns(e.ColumnIndex).Name = "Column1" Then
MessageBox.Show(String.Format("チェックボックスが[{0}]になりました。", _
IIf(Me.Column1.Selected, "On", "Off")), "DataGridView1_CellClick", MessageBoxButtons.OK)
End If
End If

End Sub




[ メッセージ編集済み 編集者: Haru 編集日時 2009-02-27 18:22 ]
くまっち
大ベテラン
会議室デビュー日: 2008/01/18
投稿数: 169
お住まい・勤務地: 茨城県のどこか。
投稿日時: 2009-03-02 11:12
提示サンプルコードの各行で何を行っているかを理解してください。

> チェックボックスと文字列の間を若干広げたいのでヒントだけでも
チェックボックスのX座標を求めている箇所を修正してください。
location.X = cellBounds.X + (cellBounds.Width - drawTextSize.Width) / 2 - 14

> チェックボックスのON/OFFが画面に反映されないのですが
Selected = Me._checkStateMyBase.Value = _checkState
どのような意図で記述したのでしょう?
DataGridViewColumnHeaderCellのSelectedプロパティは使用不可ですよ。
[MSDN]DataGridViewColumnHeaderCell.Selectedプロパティ
リファレンス等をよくみて、各プロパティを理解してください。

> ヘッダーの文字列にアクセスするにはどうすれば
ヘッダ文字列の描画は、チェックボックスの描画時に行っています。
System.Windows.Forms.CheckBoxRenderer.DrawCheckBox()の第4引数に
formattedValueを渡して、文字列描画しています。
ですので、Value値を変更することで表示文字を変更出来ます。
※だからMyBase.Value = _checkStateなんてすると、表示文字もTrue/Falseと書き換わるのですよ。

> ヘッダかどうかの判定はe.RowIndex >= 0で合ってますでしょうか?
-1:ヘッダ行、0以上:データ行 だと思いますので合ってると思います。

[追記]
> イベント内でヘッダーのチェックボックスの内容を確認する方法
例えば、カスタムヘッダセルクラスにチェック状態を返すプロパティを追加します。
CellClickイベントでは、ヘッダセルをカスタムヘッダセルへDirectCastを行い
追加したプロパティを参照することで、チェック状態を取得出来ると思います。

[ メッセージ編集済み 編集者: くまっち 編集日時 2009-03-02 11:32 ]
Haru
常連さん
会議室デビュー日: 2009/02/18
投稿数: 37
投稿日時: 2009-03-02 18:37
引用:

くまっちさんの書き込み (2009-03-02 11:12) より:
提示サンプルコードの各行で何を行っているかを理解してください。



申し訳ありません。
教えて頂いたコードを理解しようと努めてはいるのですが何分Windowsアプリの作り方について
基礎がほとんどできていないため、調べるポイントが分からずにヘルプを求めてしまいます。


引用:

> チェックボックスと文字列の間を若干広げたいのでヒントだけでも
チェックボックスのX座標を求めている箇所を修正してください。
location.X = cellBounds.X + (cellBounds.Width - drawTextSize.Width) / 2 - 14




ありがとうございます。
これもAPIを注意深く見れば分かる項目でしたね。
申し訳ありませんでした。


引用:

> チェックボックスのON/OFFが画面に反映されないのですが
Selected = Me._checkStateMyBase.Value = _checkState
どのような意図で記述したのでしょう?
DataGridViewColumnHeaderCellのSelectedプロパティは使用不可ですよ。
[MSDN]DataGridViewColumnHeaderCell.Selectedプロパティ
リファレンス等をよくみて、各プロパティを理解してください。



Selectedへ_checkStateをセットしようとしたのは、ヘッダのチェックボックスを
ON/OFFしても画面に反映されない(OnClickは実行されることをConsole.WriteLineで確認)ので
自分で設定する必要があるのかと思い追加を試みました。
これについては現在も調査中ですが、OnClick処理の中にブレークポイントを設けると
ちゃんと画面にも表示されることからデバッグ文を組み込んで調べてみたところ現在の
コード:
        MyBase.OnClick(e)
        Me.RaiseCheckBoxCheckedChanged(EventArgs.Empty)


ではPaintイベントが発生しない(?)為にヘッダのチェックボックスの状態が変わらないようです。
※OnClickの中にブレークポイントを設けたり、画面を切り替えたりすると状態が変わります。

試しにMe.RaiseCheckBoxCheckedChanged(EventArgs.Empty)を
Me.RaiseCellValueChanged(e)に変えてみたらPaintイベントが発生するようで状態が変更されました。

Paintイベントを発生させるためのコードの追加が必要なのではないでしょうか?


引用:

> ヘッダーの文字列にアクセスするにはどうすれば
ヘッダ文字列の描画は、チェックボックスの描画時に行っています。
System.Windows.Forms.CheckBoxRenderer.DrawCheckBox()の第4引数に
formattedValueを渡して、文字列描画しています。
ですので、Value値を変更することで表示文字を変更出来ます。
※だからMyBase.Value = _checkStateなんてすると、表示文字もTrue/Falseと書き換わるのですよ。



質問の仕方が悪かったようです。
DrawCheckBoxの第4引数がヘッダーの文字列というのはAPIで分かったのですが、Class内で
Paintを直接呼び出すコードは書いていないのでクラス内でこの文字列を変更する方法を
知りたかったのです。
もしかして、MyBase.Valueがヘッダの文字列なのでしょうか?
※クラスの構成がよく分かっていません。どこで勉強できますでしょうか?


引用:

> イベント内でヘッダーのチェックボックスの内容を確認する方法
例えば、カスタムヘッダセルクラスにチェック状態を返すプロパティを追加します。
CellClickイベントでは、ヘッダセルをカスタムヘッダセルへDirectCastを行い
追加したプロパティを参照することで、チェック状態を取得出来ると思います。



こんな感じで作成しました。

コード:
【クラス側】
    Public Property Status() As Boolean
        Get
            Return _checkState
        End Get
        Set(ByVal value As Boolean)
            _checkState = value
        End Set
    End Property

【Form側】
    Private Sub DataGridView1_ColumnHeaderMouseClick(ByVal sender As System.Object, 
          ByVal e As System.Windows.Forms.DataGridViewCellMouseEventArgs)
          Handles DataGridView1.ColumnHeaderMouseClick
        If DataGridView1.Columns(e.ColumnIndex).Name = "Column1" Then
            Dim cell As DataGridViewCustomCheckBoxHeaderCell = _
                DirectCast(DataGridView1.Columns(e.ColumnIndex).HeaderCell,  _
                DataGridViewCustomCheckBoxHeaderCell)

            Console.WriteLine("ROWS=" & DataGridView1.RowCount)
            Dim flag As Boolean = False
            flag = cell.Status
            Dim dRow As DataGridViewRow
            For Each dRow In DataGridView1.Rows
                dRow.Cells(0).Value = flag
                dRow.Cells(2).Value = flag
                dRow.Cells(3).Value = flag
            Next
        End If

    End Sub



概ねOKなのですが、Rows(0).Cells(0)のチェックボックスだけは書き換わりません。
何がいけないのでしょうか?
くまっち
大ベテラン
会議室デビュー日: 2008/01/18
投稿数: 169
お住まい・勤務地: 茨城県のどこか。
投稿日時: 2009-03-02 20:17
> Paintイベントを発生させるためのコードの追加が必要なのではないでしょうか?
うーん。こちらの提示サンプルでは、チェックボックスヘッダセルをクリックした際
正しく表示更新(Paint実行)されています。
Paintが呼ばれない理由は現時点の情報では特定出来ません。
何かプロパティを規定値以外に設定していたりしてますか?

> もしかして、MyBase.Valueがヘッダの文字列なのでしょうか?
DataGridViewColumnHeaderCellに関して言えばYesですね。

> Rows(0).Cells(0)のチェックボックスだけは書き換わりません
dRow.Cells(0).Value = flag の事でしょうか?
提示されたコードを見た限りでは、何が悪いのか判断出来ません。
デバッガで該当箇所で止め、意図した値になっているか確認してみてください。
Haru
常連さん
会議室デビュー日: 2009/02/18
投稿数: 37
投稿日時: 2009-03-03 11:46
引用:

何かプロパティを規定値以外に設定していたりしてますか?



特に何も設定しておりません。


引用:

> Rows(0).Cells(0)のチェックボックスだけは書き換わりません
dRow.Cells(0).Value = flag の事でしょうか?



はいそうです。

引用:

提示されたコードを見た限りでは、何が悪いのか判断出来ません。
デバッガで該当箇所で止め、意図した値になっているか確認してみてください。


自分なりにデバッガで確認していますが問題が見つけられておりません。
次のようにボタン押下でも書き換えるようにしてみましたが、同じロジックで
ありながら、ボタン押下時とチェックボックス押下時では動作が異なりました。
ボタン押下時は全てのチェックボックスが書き換わりますが、チェックボックス
クリック時はRows(0).Cells(0)がスキップされます。
(スキップというのは、元々の値が書き換わらないということです。)
コード:
    Private Sub DataGridView1_ColumnHeaderMouseClick(ByVal sender As System.Object,
        ByVal e As System.Windows.Forms.DataGridViewCellMouseEventArgs)
        Handles DataGridView1.ColumnHeaderMouseClick

        chk_Click()
    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
        Handles Button1.Click
        chk_Click()
    End Sub

    Dim flag1 As Boolean = False
    Private Sub chk_Click()
        Console.WriteLine("ROWS=" & DataGridView1.RowCount)
        flag1 = flag1 Xor True
        Dim dRow As DataGridViewRow
        For Each dRow In DataGridView1.Rows
            dRow.Cells(0).Value = flag1
            dRow.Cells(2).Value = flag1
            dRow.Cells(3).Value = flag1
        Next
    End Sub



また、先頭に列を追加して一列ずらすと全てのチェックボックスが書き換わります。
今回作成したクラス内でのみRows(0).Cells(0)に特別な意味があるように思えますが
原因が思いつきません。

スキルアップ/キャリアアップ(JOB@IT)