DataGridViewにRadioButtonを表示できるようにする

.NET Frameworkのデータグリッド( System.Windows.Forms.DataGridView )はとても便利ですが、標準で提供されている機能だけではラジオボックスを使ったカラムを追加することができません。
こういう時は、DataGridViewTextBoxColumnクラスに、”✓”とブランクを組み合わせたり、DataGridViewCheckBoxColumnクラスを使ったりして、プログラムでラジオボタンっぽい動作をさせたりする力技的「なんちゃってラジオボタン」で回避したいところですが、DataGridViewクラスの列に、ラジオボタンの表示と編集が行えるクラスを書いてみました。
実行イメージは、下図のようになります。 

DataGridViewRadioButtonColumn

データグリッドの「列の編集」でColumnTypeにDataGridViewRadioButtonColumnを割り当てると、ラジオボタンで表示されるようになります。
また、DataGridViewRadioButtonColumnクラスGroupNameプロパティに適当な文字列を設定すると、ラジオボタンのグループ化が行われます。
グループ化されたラジオボタンは、グループの中でひとつだけが選択状態となります。 

ラジオボタンの大きさは、DataGridViewCheckBoxColumnクラスに合わせて常に固定サイズで描画されるようにしています。
ボタンの大きさを変更したいときは、DataGridViewRadioButtonCellクラスの定数RADIO_SIZEを変更するか、Paintメソッドを修正してください。

(動作確認はVisual Studio 2013 Update 4で行いました。)

' This software is distributed under the license of NYSL.
' ( http://www.kmonos.net/nysl/ )
Imports System.ComponentModel

'''
'''  ラジオボタンセル(DataGridViewRadioButtonCell)のホスト
'''
Public Class DataGridViewRadioButtonColumn
    Inherits DataGridViewColumn

    '''ラジオボタンが属するグループ名
    Private _groupName As String

    '''
    '''
    '''
    Public Sub New()
        MyBase.New(New DataGridViewRadioButtonCell)
    End Sub

    '''
    '''
    '''
    '''
    Public Overrides Function Clone() As Object

        Dim col As DataGridViewRadioButtonColumn = _
            DirectCast(MyBase.Clone(), DataGridViewRadioButtonColumn)

        ' プロパティを追加したとき、この場所に追記する
        ' Cloneメソッドに記述しない場合、プロパティブラウザで表示・編集は
        ' されるが、保存されないプロパティとなる
        col.GroupName = Me.GroupName

        Return col
    End Function

    '''
    '''
    '''
    '''
    '''
    Public Overrides Property CellTemplate() As DataGridViewCell
        Get
            Return MyBase.CellTemplate
        End Get
        Set(ByVal value As DataGridViewCell)
            If Not (value Is Nothing) AndAlso _
                Not value.GetType().IsAssignableFrom(GetType(DataGridViewRadioButtonCell)) Then
                Throw New InvalidCastException("Must be a DataGridViewRadioButtonCell")
            End If
            MyBase.CellTemplate = value
        End Set
    End Property

    '''
    ''' ラジオボタンが属するグループ名
    '''
    '''
    '''
    '''
    ''' ここで設定されたグループ名により、同一行内・同一グループのチェック
    ''' 状態のラジオボタンがひとつになるよう制御される
    Public Property GroupName As String
        Get
            Return _groupName
        End Get
        Set(value As String)
            _groupName = value
        End Set
    End Property
End Class

'''
''' ラジオボタンのセルクラス
'''
'''
Public Class DataGridViewRadioButtonCell
    Inherits DataGridViewCell

    '''ラジオボタンの描画サイズ
    Private Const RADIO_SIZE As Integer = 16

    '''
    '''
    '''
    '''
    Public Sub New()
        MyBase.New()
    End Sub

    '''
    ''' 1
    '''
    '''
    '''
    Public Overrides Function Clone() As Object
        Dim cell = DirectCast(MyBase.Clone(), DataGridViewRadioButtonCell)
        Return cell
    End Function

    '''
    '''
    '''
    '''
    '''
    '''
    '''
    '''
    '''
    '''
    '''
    Protected Overrides Function GetFormattedValue(value As Object, rowIndex As Integer, ByRef cellStyle As DataGridViewCellStyle, valueTypeConverter As TypeConverter, formattedValueTypeConverter As TypeConverter, context As DataGridViewDataErrorContexts) As Object
        Return _val(value)
    End Function

    '''
    '''
    '''
    '''
    '''
    '''
    Private Shared Function _val(v As Object) As Boolean
        Try
            Return If(IsDBNull(v) OrElse IsNothing(v), False, Convert.ToBoolean(v))
        Catch ex As Exception
            Return False
        End Try
    End Function
    '''
    '''
    '''
    '''
    '''
    '''
    '''
    '''
    '''
    '''
    '''
    '''
    '''
    '''
    '''
    Protected Overrides Sub Paint(graphics As System.Drawing.Graphics, _
                                  clipBounds As System.Drawing.Rectangle, _
                                  cellBounds As System.Drawing.Rectangle, _
                                  rowIndex As Integer, _
                                  elementState As System.Windows.Forms.DataGridViewElementStates, _
                                  value As Object, _
                                  formattedValue As Object, _
                                  errorText As String, _
                                  cellStyle As System.Windows.Forms.DataGridViewCellStyle, _
                                  advancedBorderStyle As System.Windows.Forms.DataGridViewAdvancedBorderStyle, _
                                  paintParts As System.Windows.Forms.DataGridViewPaintParts)

        MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts)

        ' 背景色の描画
        If (paintParts And DataGridViewPaintParts.Background) = DataGridViewPaintParts.Background Then
            Dim brush As New SolidBrush(If(elementState And DataGridViewElementStates.Selected, _
                                           cellStyle.SelectionBackColor, cellStyle.BackColor))
            graphics.FillRectangle(brush, cellBounds)
            brush.Dispose()
        End If

        ' 境界線の描画
        If (paintParts And DataGridViewPaintParts.Border) = DataGridViewPaintParts.Border Then
            PaintBorder(graphics, clipBounds, cellBounds, cellStyle, advancedBorderStyle)
        End If

        ' セルの幅、高さがラジオボタンの描画サイズより小さい場合は、描画処理を飛ばす
        If cellBounds.Width < RADIO_SIZE OrElse cellBounds.Height < RADIO_SIZE Then
            Return
        End If

        ' ラジオボタンをセルの真ん中に描画(描画サイズは固定)
        Dim rect As Rectangle = New Rectangle(New Point(cellBounds.Left + (cellBounds.Width - RADIO_SIZE) / 2, _
                                                        cellBounds.Top + (cellBounds.Height - RADIO_SIZE) / 2), _
                                                        New Size(RADIO_SIZE, RADIO_SIZE))


        Dim v = _val(value)
        ControlPaint.DrawRadioButton(graphics, rect, If(v, ButtonState.Checked, ButtonState.Inactive))

    End Sub

    '''
    '''
    '''
    '''
    '''
    '''
    Protected Overrides Sub OnKeyPress(e As System.Windows.Forms.KeyPressEventArgs, rowIndex As Integer)

        MyBase.OnKeyPress(e, rowIndex)

        If [ReadOnly] Then
            Return
        End If

        ' スペースキーが押されたとき、ラジオボタンのチェック状態の更新
        If e.KeyChar = Microsoft.VisualBasic.ChrW(Keys.Space) Then
            updateValues(DataGridView.CurrentCell.ColumnIndex, rowIndex)
        End If

    End Sub

    '''
    '''
    '''
    '''
    '''
    Protected Overrides Sub OnClick(e As System.Windows.Forms.DataGridViewCellEventArgs)

        MyBase.OnClick(e)

        If [ReadOnly] Then
            Return
        End If

        ' ラジオボタンのチェック状態の更新
        updateValues(e.ColumnIndex, e.RowIndex)

    End Sub

    '''
    ''' ラジオボタンのチェック状態を更新
    '''
    '''
    '''
    '''
    Private Sub updateValues(columnIndex As Integer, rowIndex As Integer)

        Dim currentColumn As DataGridViewRadioButtonColumn = _
            TryCast(Me.DataGridView.Columns(columnIndex), DataGridViewRadioButtonColumn)

        For Each column As DataGridViewColumn In Me.DataGridView.Columns

            ' ラジオボタンセル以外は処理を飛ばす
            If Not (TypeOf (column) Is DataGridViewRadioButtonColumn) Then
                Continue For
            End If

            ' カレントセルと異なるグループ名は処理を飛ばす
            Dim radioColumn As DataGridViewRadioButtonColumn = TryCast(column, DataGridViewRadioButtonColumn)
            If radioColumn.GroupName <> currentColumn.GroupName Then
                Continue For
            End If

            Dim dgvc = Me.DataGridView.Rows(rowIndex).Cells(column.Index)

            ' 更新後のチェック状態を求める
            Dim v = If(radioColumn.Index = currentColumn.Index, True, False)

            ' 値の更新が生じない場合は処理を飛ばす
            Dim prev = _val(dgvc.Value)
            If prev = v Then
                Continue For
            End If

            dgvc.Value = v

            ' イベントを発生させる
            ' ※チェック状態を更新した回数分イベントが発生する
            Dim newEa = New DataGridViewCellEventArgs(columnIndex, rowIndex)
            RaiseCellValueChanged(newEa)

            Me.DataGridView.InvalidateCell(dgvc)
            Me.DataGridView.NotifyCurrentCellDirty(True)
        Next

    End Sub

    '''
    '''
    '''
    '''
    '''
    '''
    Public Overrides ReadOnly Property DefaultNewRowValue As Object
        Get
            Return False
        End Get
    End Property

    '''
    '''
    '''
    '''
    '''
    '''
    Public Overrides Property ValueType As System.Type
        Get
            Return GetType(Boolean)
        End Get
        Set(value As System.Type)
            Throw New InvalidOperationException
        End Set
    End Property

    '''
    '''
    '''
    '''
    '''
    '''
    Public Overrides ReadOnly Property FormattedValueType As Type
        Get
            Return GetType(Boolean)
        End Get
    End Property

End Class

ソースのダウンロード: DataGridViewRadioButtonColumn.zip

makoto について

デジタルガジェット大好き!! 小さい機械を片手に、JavaとFlex、PHPなど、いろいろなプログラミング言語・環境を行ったり来たりしながらプログラムを書いています。 最近お気に入りな環境は、Visual StudioとFileMaker。
カテゴリー: コードスニペット, 技術的なメモ タグ: パーマリンク

DataGridViewにRadioButtonを表示できるようにする への4件のフィードバック

  1. HH のコメント:

    大変参考になります。
    この処理を使用して、アプリを作成してみましたが、この処理を利用してラジオボタンを選択状態から開始することはできるでしょか?

    • makoto のコメント:

      コンポーネントをシステムに組み込んで使っているので、できると思います。

  2. K のコメント:

    とても参考になります、ありがとうございます。
    行ではなく、列の選択をするにはどのように変更をすればできるでしょうか?
    VBを始めたばかりです。よろしくお願いします。

    • makoto のコメント:

      コンポーネント側で状態を作るより、データソース側で設定をしてしまう方が簡単にできるのではないかと思います。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です